2020-05-07 02:36:54 +00:00
|
|
|
// Copyright (c) 2014-2020, The Monero Project
|
2015-12-15 17:28:52 +00:00
|
|
|
//
|
2014-09-17 18:55:28 +00:00
|
|
|
// All rights reserved.
|
2015-12-15 17:28:52 +00:00
|
|
|
//
|
2014-09-17 18:55:28 +00:00
|
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
|
|
// permitted provided that the following conditions are met:
|
2015-12-15 17:28:52 +00:00
|
|
|
//
|
2014-09-17 18:55:28 +00:00
|
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
|
|
// conditions and the following disclaimer.
|
2015-12-15 17:28:52 +00:00
|
|
|
//
|
2014-09-17 18:55:28 +00:00
|
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
|
|
// materials provided with the distribution.
|
2015-12-15 17:28:52 +00:00
|
|
|
//
|
2014-09-17 18:55:28 +00:00
|
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
|
|
// used to endorse or promote products derived from this software without specific
|
|
|
|
// prior written permission.
|
2015-12-15 17:28:52 +00:00
|
|
|
//
|
2014-09-17 18:55:28 +00:00
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
#include "common/dns_utils.h"
|
2014-10-06 13:00:06 +00:00
|
|
|
// check local first (in the event of static or in-source compilation of libunbound)
|
|
|
|
#include "unbound.h"
|
2014-09-17 18:55:28 +00:00
|
|
|
|
2014-10-03 13:10:21 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include "include_base_utils.h"
|
2019-03-17 15:39:15 +00:00
|
|
|
#include "common/threadpool.h"
|
2019-04-03 05:10:24 +00:00
|
|
|
#include "crypto/crypto.h"
|
2017-11-25 22:25:05 +00:00
|
|
|
#include <boost/thread/mutex.hpp>
|
2018-02-02 12:40:44 +00:00
|
|
|
#include <boost/algorithm/string/join.hpp>
|
2018-11-01 22:17:34 +00:00
|
|
|
#include <boost/optional.hpp>
|
2014-10-03 13:10:21 +00:00
|
|
|
using namespace epee;
|
2015-03-24 10:34:15 +00:00
|
|
|
|
Change logging to easylogging++
This replaces the epee and data_loggers logging systems with
a single one, and also adds filename:line and explicit severity
levels. Categories may be defined, and logging severity set
by category (or set of categories). epee style 0-4 log level
maps to a sensible severity configuration. Log files now also
rotate when reaching 100 MB.
To select which logs to output, use the MONERO_LOGS environment
variable, with a comma separated list of categories (globs are
supported), with their requested severity level after a colon.
If a log matches more than one such setting, the last one in
the configuration string applies. A few examples:
This one is (mostly) silent, only outputting fatal errors:
MONERO_LOGS=*:FATAL
This one is very verbose:
MONERO_LOGS=*:TRACE
This one is totally silent (logwise):
MONERO_LOGS=""
This one outputs all errors and warnings, except for the
"verify" category, which prints just fatal errors (the verify
category is used for logs about incoming transactions and
blocks, and it is expected that some/many will fail to verify,
hence we don't want the spam):
MONERO_LOGS=*:WARNING,verify:FATAL
Log levels are, in decreasing order of priority:
FATAL, ERROR, WARNING, INFO, DEBUG, TRACE
Subcategories may be added using prefixes and globs. This
example will output net.p2p logs at the TRACE level, but all
other net* logs only at INFO:
MONERO_LOGS=*:ERROR,net*:INFO,net.p2p:TRACE
Logs which are intended for the user (which Monero was using
a lot through epee, but really isn't a nice way to go things)
should use the "global" category. There are a few helper macros
for using this category, eg: MGINFO("this shows up by default")
or MGINFO_RED("this is red"), to try to keep a similar look
and feel for now.
Existing epee log macros still exist, and map to the new log
levels, but since they're used as a "user facing" UI element
as much as a logging system, they often don't map well to log
severities (ie, a log level 0 log may be an error, or may be
something we want the user to see, such as an important info).
In those cases, I tried to use the new macros. In other cases,
I left the existing macros in. When modifying logs, it is
probably best to switch to the new macros with explicit levels.
The --log-level options and set_log commands now also accept
category settings, in addition to the epee style log levels.
2017-01-01 16:34:23 +00:00
|
|
|
#undef MONERO_DEFAULT_LOG_CATEGORY
|
|
|
|
#define MONERO_DEFAULT_LOG_CATEGORY "net.dns"
|
|
|
|
|
2018-02-02 12:40:44 +00:00
|
|
|
static const char *DEFAULT_DNS_PUBLIC_ADDR[] =
|
|
|
|
{
|
|
|
|
};
|
2017-09-30 08:07:43 +00:00
|
|
|
|
2016-01-26 22:09:08 +00:00
|
|
|
static boost::mutex instance_lock;
|
2015-08-27 20:08:03 +00:00
|
|
|
|
2015-03-24 10:34:15 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The following two functions were taken from unbound-anchor.c, from
|
|
|
|
* the unbound library packaged with this source. The license and source
|
|
|
|
* can be found in $PROJECT_ROOT/external/unbound
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Cert builtin commented out until it's used, as the compiler complains
|
|
|
|
|
|
|
|
// return the built in root update certificate
|
|
|
|
static const char*
|
|
|
|
get_builtin_cert(void)
|
|
|
|
{
|
|
|
|
return
|
|
|
|
// The ICANN CA fetched at 24 Sep 2010. Valid to 2028
|
|
|
|
"-----BEGIN CERTIFICATE-----\n"
|
|
|
|
"MIIDdzCCAl+gAwIBAgIBATANBgkqhkiG9w0BAQsFADBdMQ4wDAYDVQQKEwVJQ0FO\n"
|
|
|
|
"TjEmMCQGA1UECxMdSUNBTk4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNV\n"
|
|
|
|
"BAMTDUlDQU5OIFJvb3QgQ0ExCzAJBgNVBAYTAlVTMB4XDTA5MTIyMzA0MTkxMloX\n"
|
|
|
|
"DTI5MTIxODA0MTkxMlowXTEOMAwGA1UEChMFSUNBTk4xJjAkBgNVBAsTHUlDQU5O\n"
|
|
|
|
"IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1JQ0FOTiBSb290IENB\n"
|
|
|
|
"MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKDb\n"
|
|
|
|
"cLhPNNqc1NB+u+oVvOnJESofYS9qub0/PXagmgr37pNublVThIzyLPGCJ8gPms9S\n"
|
|
|
|
"G1TaKNIsMI7d+5IgMy3WyPEOECGIcfqEIktdR1YWfJufXcMReZwU4v/AdKzdOdfg\n"
|
|
|
|
"ONiwc6r70duEr1IiqPbVm5T05l1e6D+HkAvHGnf1LtOPGs4CHQdpIUcy2kauAEy2\n"
|
|
|
|
"paKcOcHASvbTHK7TbbvHGPB+7faAztABLoneErruEcumetcNfPMIjXKdv1V1E3C7\n"
|
|
|
|
"MSJKy+jAqqQJqjZoQGB0necZgUMiUv7JK1IPQRM2CXJllcyJrm9WFxY0c1KjBO29\n"
|
|
|
|
"iIKK69fcglKcBuFShUECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B\n"
|
|
|
|
"Af8EBAMCAf4wHQYDVR0OBBYEFLpS6UmDJIZSL8eZzfyNa2kITcBQMA0GCSqGSIb3\n"
|
|
|
|
"DQEBCwUAA4IBAQAP8emCogqHny2UYFqywEuhLys7R9UKmYY4suzGO4nkbgfPFMfH\n"
|
|
|
|
"6M+Zj6owwxlwueZt1j/IaCayoKU3QsrYYoDRolpILh+FPwx7wseUEV8ZKpWsoDoD\n"
|
|
|
|
"2JFbLg2cfB8u/OlE4RYmcxxFSmXBg0yQ8/IoQt/bxOcEEhhiQ168H2yE5rxJMt9h\n"
|
|
|
|
"15nu5JBSewrCkYqYYmaxyOC3WrVGfHZxVI7MpIFcGdvSb2a1uyuua8l0BKgk3ujF\n"
|
|
|
|
"0/wsHNeP22qNyVO+XVBzrM8fk8BSUFuiT/6tZTYXRtEt5aKQZgXbKU5dUF3jT9qg\n"
|
|
|
|
"j/Br5BZw3X/zd325TvnswzMC1+ljLzHnQGGk\n"
|
|
|
|
"-----END CERTIFICATE-----\n"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** return the built in root DS trust anchor */
|
2018-08-21 17:31:42 +00:00
|
|
|
static const char* const*
|
2015-03-24 10:34:15 +00:00
|
|
|
get_builtin_ds(void)
|
|
|
|
{
|
2018-08-21 17:31:42 +00:00
|
|
|
static const char * const ds[] =
|
|
|
|
{
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
return ds;
|
2015-03-24 10:34:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/************************************************************
|
|
|
|
************************************************************
|
|
|
|
***********************************************************/
|
|
|
|
|
|
|
|
} // anonymous namespace
|
2014-10-03 13:10:21 +00:00
|
|
|
|
2014-09-17 18:55:28 +00:00
|
|
|
namespace tools
|
|
|
|
{
|
2014-09-17 19:35:52 +00:00
|
|
|
|
2018-11-01 22:17:34 +00:00
|
|
|
static const char *get_record_name(int record_type)
|
|
|
|
{
|
|
|
|
switch (record_type)
|
|
|
|
{
|
|
|
|
case DNS_TYPE_A: return "A";
|
|
|
|
case DNS_TYPE_TXT: return "TXT";
|
|
|
|
case DNS_TYPE_AAAA: return "AAAA";
|
|
|
|
default: return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-23 23:27:03 +00:00
|
|
|
// fuck it, I'm tired of dealing with getnameinfo()/inet_ntop/etc
|
2018-11-01 22:17:34 +00:00
|
|
|
boost::optional<std::string> ipv4_to_string(const char* src, size_t len)
|
2014-09-23 23:27:03 +00:00
|
|
|
{
|
2018-11-01 22:17:34 +00:00
|
|
|
if (len < 4)
|
|
|
|
{
|
|
|
|
MERROR("Invalid IPv4 address: " << std::string(src, len));
|
|
|
|
return boost::none;
|
|
|
|
}
|
2015-08-29 11:53:01 +00:00
|
|
|
|
2014-09-23 23:27:03 +00:00
|
|
|
std::stringstream ss;
|
|
|
|
unsigned int bytes[4];
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
{
|
|
|
|
unsigned char a = src[i];
|
|
|
|
bytes[i] = a;
|
|
|
|
}
|
|
|
|
ss << bytes[0] << "."
|
|
|
|
<< bytes[1] << "."
|
|
|
|
<< bytes[2] << "."
|
|
|
|
<< bytes[3];
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
// this obviously will need to change, but is here to reflect the above
|
|
|
|
// stop-gap measure and to make the tests pass at least...
|
2018-11-01 22:17:34 +00:00
|
|
|
boost::optional<std::string> ipv6_to_string(const char* src, size_t len)
|
2014-09-23 23:27:03 +00:00
|
|
|
{
|
2018-11-01 22:17:34 +00:00
|
|
|
if (len < 8)
|
|
|
|
{
|
|
|
|
MERROR("Invalid IPv4 address: " << std::string(src, len));
|
|
|
|
return boost::none;
|
|
|
|
}
|
2015-08-29 11:53:01 +00:00
|
|
|
|
2014-09-23 23:27:03 +00:00
|
|
|
std::stringstream ss;
|
|
|
|
unsigned int bytes[8];
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
{
|
|
|
|
unsigned char a = src[i];
|
|
|
|
bytes[i] = a;
|
|
|
|
}
|
|
|
|
ss << bytes[0] << ":"
|
|
|
|
<< bytes[1] << ":"
|
|
|
|
<< bytes[2] << ":"
|
|
|
|
<< bytes[3] << ":"
|
|
|
|
<< bytes[4] << ":"
|
|
|
|
<< bytes[5] << ":"
|
|
|
|
<< bytes[6] << ":"
|
|
|
|
<< bytes[7];
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
2018-11-01 22:17:34 +00:00
|
|
|
boost::optional<std::string> txt_to_string(const char* src, size_t len)
|
2015-08-29 11:53:01 +00:00
|
|
|
{
|
2018-11-01 22:17:34 +00:00
|
|
|
if (len == 0)
|
|
|
|
return boost::none;
|
2015-08-29 11:53:01 +00:00
|
|
|
return std::string(src+1, len-1);
|
|
|
|
}
|
|
|
|
|
2014-09-17 21:26:51 +00:00
|
|
|
// custom smart pointer.
|
|
|
|
// TODO: see if std::auto_ptr and the like support custom destructors
|
2015-06-07 15:57:01 +00:00
|
|
|
template<typename type, void (*freefunc)(type*)>
|
|
|
|
class scoped_ptr
|
2014-09-17 21:26:51 +00:00
|
|
|
{
|
|
|
|
public:
|
2015-06-07 15:57:01 +00:00
|
|
|
scoped_ptr():
|
|
|
|
ptr(nullptr)
|
2014-09-17 21:26:51 +00:00
|
|
|
{
|
|
|
|
}
|
2015-06-07 15:57:01 +00:00
|
|
|
scoped_ptr(type *p):
|
|
|
|
ptr(p)
|
2014-09-17 21:26:51 +00:00
|
|
|
{
|
|
|
|
}
|
2015-06-07 15:57:01 +00:00
|
|
|
~scoped_ptr()
|
|
|
|
{
|
|
|
|
freefunc(ptr);
|
|
|
|
}
|
|
|
|
operator type *() { return ptr; }
|
|
|
|
type **operator &() { return &ptr; }
|
|
|
|
type *operator->() { return ptr; }
|
|
|
|
operator const type*() const { return &ptr; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
type* ptr;
|
2014-09-17 21:26:51 +00:00
|
|
|
};
|
|
|
|
|
2015-06-07 15:57:01 +00:00
|
|
|
typedef class scoped_ptr<ub_result,ub_resolve_free> ub_result_ptr;
|
|
|
|
|
2014-09-17 19:35:52 +00:00
|
|
|
struct DNSResolverData
|
|
|
|
{
|
|
|
|
ub_ctx* m_ub_context;
|
|
|
|
};
|
|
|
|
|
2016-02-21 14:15:53 +00:00
|
|
|
// work around for bug https://www.nlnetlabs.nl/bugs-script/show_bug.cgi?id=515 needed for it to compile on e.g. Debian 7
|
|
|
|
class string_copy {
|
|
|
|
public:
|
|
|
|
string_copy(const char *s): str(strdup(s)) {}
|
|
|
|
~string_copy() { free(str); }
|
|
|
|
operator char*() { return str; }
|
|
|
|
|
|
|
|
public:
|
|
|
|
char *str;
|
|
|
|
};
|
|
|
|
|
2019-02-25 22:16:18 +00:00
|
|
|
static void add_anchors(ub_ctx *ctx)
|
|
|
|
{
|
|
|
|
const char * const *ds = ::get_builtin_ds();
|
|
|
|
while (*ds)
|
|
|
|
{
|
|
|
|
MINFO("adding trust anchor: " << *ds);
|
|
|
|
ub_ctx_add_ta(ctx, string_copy(*ds++));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-17 19:35:52 +00:00
|
|
|
DNSResolver::DNSResolver() : m_data(new DNSResolverData())
|
2014-09-17 18:55:28 +00:00
|
|
|
{
|
2015-12-15 17:23:17 +00:00
|
|
|
int use_dns_public = 0;
|
2018-02-02 12:40:44 +00:00
|
|
|
std::vector<std::string> dns_public_addr;
|
2019-02-25 22:16:18 +00:00
|
|
|
const char *DNS_PUBLIC = getenv("DNS_PUBLIC");
|
|
|
|
if (DNS_PUBLIC)
|
2015-12-15 17:23:17 +00:00
|
|
|
{
|
2019-02-25 22:16:18 +00:00
|
|
|
dns_public_addr = tools::dns_utils::parse_dns_public(DNS_PUBLIC);
|
2017-09-30 08:07:43 +00:00
|
|
|
if (!dns_public_addr.empty())
|
2015-12-15 17:23:17 +00:00
|
|
|
{
|
2018-02-02 12:40:44 +00:00
|
|
|
MGINFO("Using public DNS server(s): " << boost::join(dns_public_addr, ", ") << " (TCP)");
|
2015-12-15 17:23:17 +00:00
|
|
|
use_dns_public = 1;
|
|
|
|
}
|
2017-09-30 08:07:43 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
MERROR("Failed to parse DNS_PUBLIC");
|
|
|
|
}
|
2015-12-15 17:23:17 +00:00
|
|
|
}
|
|
|
|
|
2014-09-17 19:35:52 +00:00
|
|
|
// init libunbound context
|
|
|
|
m_data->m_ub_context = ub_ctx_create();
|
2014-09-17 18:55:28 +00:00
|
|
|
|
2015-12-15 17:23:17 +00:00
|
|
|
if (use_dns_public)
|
|
|
|
{
|
2018-02-02 12:40:44 +00:00
|
|
|
for (const auto &ip: dns_public_addr)
|
2018-04-10 22:37:33 +00:00
|
|
|
ub_ctx_set_fwd(m_data->m_ub_context, string_copy(ip.c_str()));
|
2016-02-21 14:15:53 +00:00
|
|
|
ub_ctx_set_option(m_data->m_ub_context, string_copy("do-udp:"), string_copy("no"));
|
|
|
|
ub_ctx_set_option(m_data->m_ub_context, string_copy("do-tcp:"), string_copy("yes"));
|
2015-12-15 17:23:17 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// look for "/etc/resolv.conf" and "/etc/hosts" or platform equivalent
|
|
|
|
ub_ctx_resolvconf(m_data->m_ub_context, NULL);
|
|
|
|
ub_ctx_hosts(m_data->m_ub_context, NULL);
|
|
|
|
}
|
2015-03-24 10:34:15 +00:00
|
|
|
|
2019-02-25 22:16:18 +00:00
|
|
|
add_anchors(m_data->m_ub_context);
|
|
|
|
|
|
|
|
if (!DNS_PUBLIC)
|
2018-08-21 17:31:42 +00:00
|
|
|
{
|
2019-02-25 22:16:18 +00:00
|
|
|
// if no DNS_PUBLIC specified, we try a lookup to what we know
|
|
|
|
// should be a valid DNSSEC record, and switch to known good
|
|
|
|
// DNSSEC resolvers if verification fails
|
|
|
|
bool available, valid;
|
|
|
|
static const char *probe_hostname = "updates.moneropulse.org";
|
|
|
|
auto records = get_txt_record(probe_hostname, available, valid);
|
|
|
|
if (!valid)
|
|
|
|
{
|
|
|
|
MINFO("Failed to verify DNSSEC record from " << probe_hostname << ", falling back to TCP with well known DNSSEC resolvers");
|
|
|
|
ub_ctx_delete(m_data->m_ub_context);
|
|
|
|
m_data->m_ub_context = ub_ctx_create();
|
|
|
|
add_anchors(m_data->m_ub_context);
|
2019-03-20 23:43:51 +00:00
|
|
|
for (const auto &ip: DEFAULT_DNS_PUBLIC_ADDR)
|
|
|
|
ub_ctx_set_fwd(m_data->m_ub_context, string_copy(ip));
|
2019-02-25 22:16:18 +00:00
|
|
|
ub_ctx_set_option(m_data->m_ub_context, string_copy("do-udp:"), string_copy("no"));
|
|
|
|
ub_ctx_set_option(m_data->m_ub_context, string_copy("do-tcp:"), string_copy("yes"));
|
|
|
|
}
|
2018-08-21 17:31:42 +00:00
|
|
|
}
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DNSResolver::~DNSResolver()
|
|
|
|
{
|
|
|
|
if (m_data)
|
2014-09-17 18:55:28 +00:00
|
|
|
{
|
2014-09-17 19:35:52 +00:00
|
|
|
if (m_data->m_ub_context != NULL)
|
|
|
|
{
|
|
|
|
ub_ctx_delete(m_data->m_ub_context);
|
|
|
|
}
|
|
|
|
delete m_data;
|
2014-09-17 18:55:28 +00:00
|
|
|
}
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
2014-09-17 18:55:28 +00:00
|
|
|
|
2018-11-01 22:17:34 +00:00
|
|
|
std::vector<std::string> DNSResolver::get_record(const std::string& url, int record_type, boost::optional<std::string> (*reader)(const char *,size_t), bool& dnssec_available, bool& dnssec_valid)
|
2014-09-17 19:35:52 +00:00
|
|
|
{
|
2014-09-18 13:07:46 +00:00
|
|
|
std::vector<std::string> addresses;
|
2014-09-25 04:14:22 +00:00
|
|
|
dnssec_available = false;
|
|
|
|
dnssec_valid = false;
|
2014-09-18 13:07:46 +00:00
|
|
|
|
2015-08-29 11:55:43 +00:00
|
|
|
if (!check_address_syntax(url.c_str()))
|
2014-09-18 13:07:46 +00:00
|
|
|
{
|
|
|
|
return addresses;
|
|
|
|
}
|
|
|
|
|
2014-09-17 21:26:51 +00:00
|
|
|
// destructor takes care of cleanup
|
|
|
|
ub_result_ptr result;
|
|
|
|
|
2014-09-17 19:35:52 +00:00
|
|
|
// call DNS resolver, blocking. if return value not zero, something went wrong
|
2016-02-21 14:15:53 +00:00
|
|
|
if (!ub_resolve(m_data->m_ub_context, string_copy(url.c_str()), record_type, DNS_CLASS_IN, &result))
|
2014-09-17 18:55:28 +00:00
|
|
|
{
|
2018-11-23 13:47:51 +00:00
|
|
|
dnssec_available = (result->secure || result->bogus);
|
2015-06-20 20:02:13 +00:00
|
|
|
dnssec_valid = result->secure && !result->bogus;
|
2015-06-07 15:57:01 +00:00
|
|
|
if (result->havedata)
|
2014-09-17 19:35:52 +00:00
|
|
|
{
|
2015-06-07 15:57:01 +00:00
|
|
|
for (size_t i=0; result->data[i] != NULL; i++)
|
2014-09-17 19:35:52 +00:00
|
|
|
{
|
2018-11-01 22:17:34 +00:00
|
|
|
boost::optional<std::string> res = (*reader)(result->data[i], result->len[i]);
|
|
|
|
if (res)
|
|
|
|
{
|
|
|
|
MINFO("Found \"" << *res << "\" in " << get_record_name(record_type) << " record for " << url);
|
|
|
|
addresses.push_back(*res);
|
|
|
|
}
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
|
|
|
}
|
2014-09-17 18:55:28 +00:00
|
|
|
}
|
|
|
|
|
2014-09-18 13:07:46 +00:00
|
|
|
return addresses;
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
|
|
|
|
2015-08-29 11:53:01 +00:00
|
|
|
std::vector<std::string> DNSResolver::get_ipv4(const std::string& url, bool& dnssec_available, bool& dnssec_valid)
|
2014-09-17 19:35:52 +00:00
|
|
|
{
|
2015-08-29 11:53:01 +00:00
|
|
|
return get_record(url, DNS_TYPE_A, ipv4_to_string, dnssec_available, dnssec_valid);
|
|
|
|
}
|
2014-09-17 19:35:52 +00:00
|
|
|
|
2015-08-29 11:53:01 +00:00
|
|
|
std::vector<std::string> DNSResolver::get_ipv6(const std::string& url, bool& dnssec_available, bool& dnssec_valid)
|
|
|
|
{
|
|
|
|
return get_record(url, DNS_TYPE_AAAA, ipv6_to_string, dnssec_available, dnssec_valid);
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
|
|
|
|
2014-09-25 04:14:22 +00:00
|
|
|
std::vector<std::string> DNSResolver::get_txt_record(const std::string& url, bool& dnssec_available, bool& dnssec_valid)
|
2014-09-17 19:35:52 +00:00
|
|
|
{
|
2015-08-29 11:53:01 +00:00
|
|
|
return get_record(url, DNS_TYPE_TXT, txt_to_string, dnssec_available, dnssec_valid);
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
|
|
|
|
2015-05-19 09:33:30 +00:00
|
|
|
std::string DNSResolver::get_dns_format_from_oa_address(const std::string& oa_addr)
|
|
|
|
{
|
|
|
|
std::string addr(oa_addr);
|
|
|
|
auto first_at = addr.find("@");
|
|
|
|
if (first_at == std::string::npos)
|
|
|
|
return addr;
|
|
|
|
|
|
|
|
// convert name@domain.tld to name.domain.tld
|
|
|
|
addr.replace(first_at, 1, ".");
|
|
|
|
|
|
|
|
return addr;
|
|
|
|
}
|
|
|
|
|
2014-09-17 19:35:52 +00:00
|
|
|
DNSResolver& DNSResolver::instance()
|
|
|
|
{
|
2016-01-26 22:09:08 +00:00
|
|
|
boost::lock_guard<boost::mutex> lock(instance_lock);
|
2015-08-27 20:08:03 +00:00
|
|
|
|
2017-06-28 21:21:06 +00:00
|
|
|
static DNSResolver staticInstance;
|
|
|
|
return staticInstance;
|
2014-09-17 19:35:52 +00:00
|
|
|
}
|
2014-09-17 18:55:28 +00:00
|
|
|
|
2015-08-27 20:06:09 +00:00
|
|
|
DNSResolver DNSResolver::create()
|
|
|
|
{
|
|
|
|
return DNSResolver();
|
|
|
|
}
|
|
|
|
|
2015-08-27 20:08:40 +00:00
|
|
|
bool DNSResolver::check_address_syntax(const char *addr) const
|
2014-09-18 13:07:46 +00:00
|
|
|
{
|
|
|
|
// if string doesn't contain a dot, we won't consider it a url for now.
|
2015-06-07 15:57:01 +00:00
|
|
|
if (strchr(addr,'.') == NULL)
|
2014-09-18 13:07:46 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-12-16 03:06:50 +00:00
|
|
|
namespace dns_utils
|
|
|
|
{
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
// TODO: parse the string in a less stupid way, probably with regex
|
|
|
|
std::string address_from_txt_record(const std::string& s)
|
|
|
|
{
|
|
|
|
// make sure the txt record has "oa1:xmr" and find it
|
|
|
|
auto pos = s.find("oa1:xmr");
|
|
|
|
if (pos == std::string::npos)
|
|
|
|
return {};
|
|
|
|
// search from there to find "recipient_address="
|
|
|
|
pos = s.find("recipient_address=", pos);
|
|
|
|
if (pos == std::string::npos)
|
|
|
|
return {};
|
|
|
|
pos += 18; // move past "recipient_address="
|
|
|
|
// find the next semicolon
|
|
|
|
auto pos2 = s.find(";", pos);
|
|
|
|
if (pos2 != std::string::npos)
|
|
|
|
{
|
|
|
|
// length of address == 95, we can at least validate that much here
|
|
|
|
if (pos2 - pos == 95)
|
|
|
|
{
|
|
|
|
return s.substr(pos, 95);
|
|
|
|
}
|
|
|
|
else if (pos2 - pos == 106) // length of address == 106 --> integrated address
|
|
|
|
{
|
|
|
|
return s.substr(pos, 106);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @brief gets a monero address from the TXT record of a DNS entry
|
|
|
|
*
|
|
|
|
* gets the monero address from the TXT record of the DNS entry associated
|
|
|
|
* with <url>. If this lookup fails, or the TXT record does not contain an
|
|
|
|
* XMR address in the correct format, returns an empty string. <dnssec_valid>
|
|
|
|
* will be set true or false according to whether or not the DNS query passes
|
|
|
|
* DNSSEC validation.
|
|
|
|
*
|
|
|
|
* @param url the url to look up
|
|
|
|
* @param dnssec_valid return-by-reference for DNSSEC status of query
|
|
|
|
*
|
|
|
|
* @return a monero address (as a string) or an empty string
|
|
|
|
*/
|
|
|
|
std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid)
|
|
|
|
{
|
|
|
|
std::vector<std::string> addresses;
|
|
|
|
// get txt records
|
|
|
|
bool dnssec_available, dnssec_isvalid;
|
|
|
|
std::string oa_addr = DNSResolver::instance().get_dns_format_from_oa_address(url);
|
|
|
|
auto records = DNSResolver::instance().get_txt_record(oa_addr, dnssec_available, dnssec_isvalid);
|
|
|
|
|
|
|
|
// TODO: update this to allow for conveying that dnssec was not available
|
|
|
|
if (dnssec_available && dnssec_isvalid)
|
|
|
|
{
|
|
|
|
dnssec_valid = true;
|
|
|
|
}
|
|
|
|
else dnssec_valid = false;
|
|
|
|
|
|
|
|
// for each txt record, try to find a monero address in it.
|
|
|
|
for (auto& rec : records)
|
|
|
|
{
|
|
|
|
std::string addr = address_from_txt_record(rec);
|
|
|
|
if (addr.size())
|
|
|
|
{
|
|
|
|
addresses.push_back(addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return addresses;
|
|
|
|
}
|
|
|
|
|
2017-07-27 10:28:53 +00:00
|
|
|
std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm)
|
2016-12-16 03:06:50 +00:00
|
|
|
{
|
|
|
|
// attempt to get address from dns query
|
|
|
|
auto addresses = addresses_from_url(url, dnssec_valid);
|
|
|
|
if (addresses.empty())
|
|
|
|
{
|
2017-03-16 23:04:17 +00:00
|
|
|
LOG_ERROR("wrong address: " << url);
|
2016-12-16 03:06:50 +00:00
|
|
|
return {};
|
|
|
|
}
|
2017-07-27 10:28:53 +00:00
|
|
|
return dns_confirm(url, addresses, dnssec_valid);
|
2016-12-16 03:06:50 +00:00
|
|
|
}
|
|
|
|
|
2017-02-16 20:03:26 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
bool dns_records_match(const std::vector<std::string>& a, const std::vector<std::string>& b)
|
|
|
|
{
|
|
|
|
if (a.size() != b.size()) return false;
|
|
|
|
|
|
|
|
for (const auto& record_in_a : a)
|
|
|
|
{
|
|
|
|
bool ok = false;
|
|
|
|
for (const auto& record_in_b : b)
|
|
|
|
{
|
|
|
|
if (record_in_a == record_in_b)
|
|
|
|
{
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!ok) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std::vector<std::string> &dns_urls)
|
|
|
|
{
|
2017-02-21 14:38:22 +00:00
|
|
|
// Prevent infinite recursion when distributing
|
|
|
|
if (dns_urls.empty()) return false;
|
|
|
|
|
2017-02-16 20:03:26 +00:00
|
|
|
std::vector<std::vector<std::string> > records;
|
|
|
|
records.resize(dns_urls.size());
|
|
|
|
|
2019-04-03 05:10:24 +00:00
|
|
|
size_t first_index = crypto::rand_idx(dns_urls.size());
|
2017-02-16 20:03:26 +00:00
|
|
|
|
2017-09-21 08:23:08 +00:00
|
|
|
// send all requests in parallel
|
|
|
|
std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false);
|
2019-03-17 15:39:15 +00:00
|
|
|
tools::threadpool& tpool = tools::threadpool::getInstance();
|
2020-08-12 22:13:29 +00:00
|
|
|
tools::threadpool::waiter waiter(tpool);
|
2017-09-21 08:23:08 +00:00
|
|
|
for (size_t n = 0; n < dns_urls.size(); ++n)
|
|
|
|
{
|
2019-03-17 15:39:15 +00:00
|
|
|
tpool.submit(&waiter,[n, dns_urls, &records, &avail, &valid](){
|
2017-09-21 08:23:08 +00:00
|
|
|
records[n] = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]);
|
|
|
|
});
|
|
|
|
}
|
2020-08-12 22:13:29 +00:00
|
|
|
waiter.wait();
|
2017-09-21 08:23:08 +00:00
|
|
|
|
2017-02-16 20:03:26 +00:00
|
|
|
size_t cur_index = first_index;
|
|
|
|
do
|
|
|
|
{
|
2017-09-21 08:23:08 +00:00
|
|
|
const std::string &url = dns_urls[cur_index];
|
|
|
|
if (!avail[cur_index])
|
2017-02-16 20:03:26 +00:00
|
|
|
{
|
|
|
|
records[cur_index].clear();
|
2019-02-21 17:37:16 +00:00
|
|
|
LOG_PRINT_L2("DNSSEC not available for hostname: " << url << ", skipping.");
|
2017-02-16 20:03:26 +00:00
|
|
|
}
|
2017-09-21 08:23:08 +00:00
|
|
|
if (!valid[cur_index])
|
2017-02-16 20:03:26 +00:00
|
|
|
{
|
|
|
|
records[cur_index].clear();
|
2019-02-21 17:37:16 +00:00
|
|
|
LOG_PRINT_L2("DNSSEC validation failed for hostname: " << url << ", skipping.");
|
2017-02-16 20:03:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cur_index++;
|
|
|
|
if (cur_index == dns_urls.size())
|
|
|
|
{
|
|
|
|
cur_index = 0;
|
|
|
|
}
|
|
|
|
} while (cur_index != first_index);
|
|
|
|
|
|
|
|
size_t num_valid_records = 0;
|
|
|
|
|
|
|
|
for( const auto& record_set : records)
|
|
|
|
{
|
|
|
|
if (record_set.size() != 0)
|
|
|
|
{
|
|
|
|
num_valid_records++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_valid_records < 2)
|
|
|
|
{
|
2019-02-21 17:37:16 +00:00
|
|
|
LOG_PRINT_L0("WARNING: no two valid DNS TXT records were received");
|
2017-02-16 20:03:26 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int good_records_index = -1;
|
|
|
|
for (size_t i = 0; i < records.size() - 1; ++i)
|
|
|
|
{
|
|
|
|
if (records[i].size() == 0) continue;
|
|
|
|
|
|
|
|
for (size_t j = i + 1; j < records.size(); ++j)
|
|
|
|
{
|
|
|
|
if (dns_records_match(records[i], records[j]))
|
|
|
|
{
|
|
|
|
good_records_index = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (good_records_index >= 0) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (good_records_index < 0)
|
|
|
|
{
|
2019-02-21 17:37:16 +00:00
|
|
|
LOG_PRINT_L0("WARNING: no two DNS TXT records matched");
|
2017-02-16 20:03:26 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
good_records = records[good_records_index];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-02 12:40:44 +00:00
|
|
|
std::vector<std::string> parse_dns_public(const char *s)
|
2017-09-30 08:07:43 +00:00
|
|
|
{
|
|
|
|
unsigned ip0, ip1, ip2, ip3;
|
|
|
|
char c;
|
2018-02-02 12:40:44 +00:00
|
|
|
std::vector<std::string> dns_public_addr;
|
2017-09-30 08:07:43 +00:00
|
|
|
if (!strcmp(s, "tcp"))
|
|
|
|
{
|
2018-02-02 12:40:44 +00:00
|
|
|
for (size_t i = 0; i < sizeof(DEFAULT_DNS_PUBLIC_ADDR) / sizeof(DEFAULT_DNS_PUBLIC_ADDR[0]); ++i)
|
|
|
|
dns_public_addr.push_back(DEFAULT_DNS_PUBLIC_ADDR[i]);
|
|
|
|
LOG_PRINT_L0("Using default public DNS server(s): " << boost::join(dns_public_addr, ", ") << " (TCP)");
|
2017-09-30 08:07:43 +00:00
|
|
|
}
|
|
|
|
else if (sscanf(s, "tcp://%u.%u.%u.%u%c", &ip0, &ip1, &ip2, &ip3, &c) == 4)
|
|
|
|
{
|
|
|
|
if (ip0 > 255 || ip1 > 255 || ip2 > 255 || ip3 > 255)
|
|
|
|
{
|
|
|
|
MERROR("Invalid IP: " << s << ", using default");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-02-02 12:40:44 +00:00
|
|
|
dns_public_addr.push_back(std::string(s + strlen("tcp://")));
|
2017-09-30 08:07:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
MERROR("Invalid DNS_PUBLIC contents, ignored");
|
|
|
|
}
|
|
|
|
return dns_public_addr;
|
|
|
|
}
|
|
|
|
|
2016-12-16 03:06:50 +00:00
|
|
|
} // namespace tools::dns_utils
|
|
|
|
|
2014-09-17 18:55:28 +00:00
|
|
|
} // namespace tools
|