diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/CMakeLists.txt | 9 | ||||
-rw-r--r-- | src/common/command_line.cpp | 5 | ||||
-rw-r--r-- | src/common/command_line.h | 1 | ||||
-rw-r--r-- | src/common/dns_utils.cpp | 102 | ||||
-rw-r--r-- | src/common/dns_utils.h | 2 | ||||
-rw-r--r-- | src/common/download.cpp | 138 | ||||
-rw-r--r-- | src/common/download.h | 36 | ||||
-rw-r--r-- | src/common/updates.cpp | 114 | ||||
-rw-r--r-- | src/common/updates.h | 37 | ||||
-rw-r--r-- | src/common/util.cpp | 52 | ||||
-rw-r--r-- | src/common/util.h | 3 |
11 files changed, 496 insertions, 3 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c63d9d0ae..eb4d4c25d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -30,12 +30,14 @@ set(common_sources base58.cpp command_line.cpp dns_utils.cpp + download.cpp util.cpp i18n.cpp password.cpp perf_timer.cpp task_region.cpp - thread_group.cpp) + thread_group.cpp + updates.cpp) if (STACK_TRACE) list(APPEND common_sources stack_trace.cpp) @@ -49,6 +51,7 @@ set(common_private_headers command_line.h common_fwd.h dns_utils.h + download.h http_connection.h int-util.h pod-class.h @@ -62,7 +65,8 @@ set(common_private_headers perf_timer.h stack_trace.h task_region.h - thread_group.h) + thread_group.h + updates.h) monero_private_headers(common ${common_private_headers}) @@ -74,6 +78,7 @@ target_link_libraries(common PUBLIC epee crypto + -lcrypto ${UNBOUND_LIBRARY} ${LIBUNWIND_LIBRARIES} ${Boost_DATE_TIME_LIBRARY} diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index c3df5c096..8739a93cd 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -120,4 +120,9 @@ namespace command_line , "How many blocks to sync at once during chain synchronization." , BLOCKS_SYNCHRONIZING_DEFAULT_COUNT }; + const command_line::arg_descriptor<std::string> arg_check_updates = { + "check-updates" + , "Check for new versions of monero: [disabled|notify|download|update]" + , "notify" + }; } diff --git a/src/common/command_line.h b/src/common/command_line.h index a09365a6b..f10e68e13 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -218,4 +218,5 @@ namespace command_line extern const arg_descriptor<uint64_t> arg_prep_blocks_threads; extern const arg_descriptor<uint64_t> arg_show_time_stats; extern const arg_descriptor<size_t> arg_block_sync_size; + extern const arg_descriptor<std::string> arg_check_updates; } diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index 5ff39574c..f7655e3c7 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -37,6 +37,7 @@ #include <stdlib.h> #include "include_base_utils.h" +#include <random> #include <boost/filesystem/fstream.hpp> using namespace epee; namespace bf = boost::filesystem; @@ -451,6 +452,107 @@ std::string get_account_address_as_str_from_url(const std::string& url, bool& dn return addresses[0]; } +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) +{ + std::vector<std::vector<std::string> > records; + records.resize(dns_urls.size()); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<int> dis(0, dns_urls.size() - 1); + size_t first_index = dis(gen); + + bool avail, valid; + size_t cur_index = first_index; + do + { + std::string url = dns_urls[cur_index]; + + records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); + if (!avail) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); + } + if (!valid) + { + records[cur_index].clear(); + LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); + } + + 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) + { + LOG_PRINT_L0("WARNING: no two valid MoneroPulse DNS checkpoint records were received"); + 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) + { + LOG_PRINT_L0("WARNING: no two MoneroPulse DNS checkpoint records matched"); + return false; + } + + good_records = records[good_records_index]; + return true; +} + } // namespace tools::dns_utils } // namespace tools diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index 6ecf5595c..2e881f0e0 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -165,6 +165,8 @@ std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid); +bool load_txt_records_from_dns(std::vector<std::string> &records, const std::vector<std::string> &dns_urls); + } // namespace tools::dns_utils } // namespace tools diff --git a/src/common/download.cpp b/src/common/download.cpp new file mode 100644 index 000000000..c5ee797d0 --- /dev/null +++ b/src/common/download.cpp @@ -0,0 +1,138 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 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. +// +// 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. +// +// 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 <string> +#include <boost/filesystem.hpp> +#include <boost/asio.hpp> +#include <boost/thread/thread.hpp> +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "net/http_client.h" +#include "download.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.dl" + +namespace tools +{ + static bool download_thread(const std::string &path, const std::string &url) + { + try + { + MINFO("Downloading " << url << " to " << path); + std::ofstream f; + f.open(path, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + if (!f.good()) { + MERROR("Failed to open file " << path); + return false; + } + class download_client: public epee::net_utils::http::http_simple_client + { + public: + download_client(std::ofstream &f): f(f) {} + virtual ~download_client() { f.close(); } + virtual bool handle_target_data(std::string &piece_of_transfer) + { + try + { + f << piece_of_transfer; + return f.good(); + } + catch (const std::exception &e) + { + MERROR("Error writing data: " << e.what()); + return false; + } + } + private: + std::ofstream &f; + } client(f); + epee::net_utils::http::url_content u_c; + if (!epee::net_utils::parse_url(url, u_c)) + { + MWARNING("Failed to parse URL " << url); + return false; + } + if (u_c.host.empty()) + { + MWARNING("Failed to determine address from URL " << url); + return false; + } + uint16_t port = u_c.port ? u_c.port : 80; + MDEBUG("Connecting to " << u_c.host << ":" << port); + client.set_server(u_c.host, std::to_string(port), boost::none); + if (!client.connect(std::chrono::seconds(30))) + { + MERROR("Failed to connect to " << url); + return false; + } + MDEBUG("GETting " << u_c.uri); + const epee::net_utils::http::http_response_info *info = NULL; + if (!client.invoke_get(u_c.uri, std::chrono::seconds(30), "", &info)) + { + MERROR("Failed to connect to " << url); + client.disconnect(); + return false; + } + if (!info) + { + MERROR("Failed invoking GET command to " << url << ", no status info returned"); + client.disconnect(); + return false; + } + MDEBUG("response code: " << info->m_response_code); + MDEBUG("response comment: " << info->m_response_comment); + MDEBUG("response body: " << info->m_body); + for (const auto &f: info->m_additional_fields) + MDEBUG("additional field: " << f.first << ": " << f.second); + if (info->m_response_code != 200) + { + MERROR("Status code " << info->m_response_code); + client.disconnect(); + return false; + } + client.disconnect(); + f.close(); + MDEBUG("Download complete"); + return true; + } + catch (const std::exception &e) + { + MERROR("Exception in download thread: " << e.what()); + return false; + } + } + + bool download(const std::string &path, const std::string &url) + { + bool success; + std::unique_ptr<boost::thread> thread(new boost::thread([&](){ success = download_thread(path, url); })); + thread->join(); + return success; + } +} diff --git a/src/common/download.h b/src/common/download.h new file mode 100644 index 000000000..ab7644689 --- /dev/null +++ b/src/common/download.h @@ -0,0 +1,36 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 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. +// +// 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. +// +// 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. + +#pragma once + +#include <string> + +namespace tools +{ + bool download(const std::string &path, const std::string &url); +} diff --git a/src/common/updates.cpp b/src/common/updates.cpp new file mode 100644 index 000000000..936106881 --- /dev/null +++ b/src/common/updates.cpp @@ -0,0 +1,114 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 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. +// +// 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. +// +// 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 "util.h" +#include "dns_utils.h" +#include "updates.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "updates" + +namespace tools +{ + bool check_updates(const std::string &software, const std::string &buildtag, bool testnet, std::string &version, std::string &hash) + { + std::vector<std::string> records; + bool found = false; + + MDEBUG("Checking updates for " << buildtag << " " << software); + + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector<std::string> dns_urls = { + }; + + static const std::vector<std::string> testnet_dns_urls = { "testver.moneropulse.net" + }; + + if (!tools::dns_utils::load_txt_records_from_dns(records, testnet ? testnet_dns_urls : dns_urls)) + return false; + + for (const auto& record : records) + { + std::vector<std::string> fields; + boost::split(fields, record, boost::is_any_of(":")); + if (fields.size() != 4) + { + MWARNING("Updates record does not have 4 fields: " << record); + continue; + } + + if (software != fields[0] || buildtag != fields[1]) + continue; + + bool alnum = true; + for (auto c: hash) + if (!isalnum(c)) + alnum = false; + if (hash.size() != 64 && !alnum) + { + MWARNING("Invalid hash: " << hash); + continue; + } + + // use highest version + if (found) + { + int cmp = vercmp(version.c_str(), fields[2].c_str()); + if (cmp > 0) + continue; + if (cmp == 0 && hash != fields[3]) + MWARNING("Two matches found for " << software << " version " << version << " on " << buildtag); + } + + version = fields[2]; + hash = fields[3]; + + MINFO("Found new version " << version << " with hash " << hash); + found = true; + } + return found; + } + + std::string get_update_url(const std::string &software, const std::string &subdir, const std::string &buildtag, const std::string &version) + { + static const char base[] = "https://downloads.getmonero.org/"; +#ifdef _WIN32 + static const char extension[] = ".zip"; +#else + static const char extension[] = ".tar.bz2"; +#endif + + std::string url; + + url = base; + if (!subdir.empty()) + url += subdir + "/"; + url = url + software + "-" + buildtag + "-v" + version + extension; + return url; + } +} diff --git a/src/common/updates.h b/src/common/updates.h new file mode 100644 index 000000000..1a70e06fd --- /dev/null +++ b/src/common/updates.h @@ -0,0 +1,37 @@ +// Copyright (c) 2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 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. +// +// 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. +// +// 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. + +#pragma once + +#include <string> + +namespace tools +{ + bool check_updates(const std::string &software, const std::string &buildtag, bool bestnet, std::string &version, std::string &hash); + std::string get_update_url(const std::string &software, const std::string &subdir, const std::string &buildtag, const std::string &version); +} diff --git a/src/common/util.cpp b/src/common/util.cpp index bfcf86bc6..90748ddb1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -31,6 +31,7 @@ #include <cstdio> #include "include_base_utils.h" +#include "file_io_utils.h" using namespace epee; #include "util.h" @@ -46,7 +47,7 @@ using namespace epee; #endif #include <boost/filesystem.hpp> #include <boost/asio.hpp> - +#include <openssl/sha.h> namespace tools { @@ -568,4 +569,53 @@ std::string get_nix_version_display_string() MDEBUG("Address '" << address << "' is not local"); return false; } + int vercmp(const char *v0, const char *v1) + { + std::vector<std::string> f0, f1; + boost::split(f0, v0, boost::is_any_of(".")); + boost::split(f1, v1, boost::is_any_of(".")); + while (f0.size() < f1.size()) + f0.push_back("0"); + while (f1.size() < f0.size()) + f1.push_back("0"); + for (size_t i = 0; i < f0.size(); ++i) { + int f0i = atoi(f0[i].c_str()), f1i = atoi(f1[i].c_str()); + int n = f0i - f1i; + if (n) + return n; + } + return 0; + } + + bool sha256sum(const std::string &filename, crypto::hash &hash) + { + if (!epee::file_io_utils::is_file_exist(filename)) + return false; + std::ifstream f; + f.exceptions(std::ifstream::failbit | std::ifstream::badbit); + f.open(filename, std::ios_base::binary | std::ios_base::in | std::ios::ate); + if (!f) + return false; + std::ifstream::pos_type file_size = f.tellg(); + SHA256_CTX ctx; + if (!SHA256_Init(&ctx)) + return false; + size_t size_left = file_size; + f.seekg(0, std::ios::beg); + while (size_left) + { + char buf[4096]; + std::ifstream::pos_type read_size = size_left > sizeof(buf) ? sizeof(buf) : size_left; + f.read(buf, read_size); + if (!f || !f.good()) + return false; + if (!SHA256_Update(&ctx, buf, read_size)) + return false; + size_left -= read_size; + } + f.close(); + if (!SHA256_Final((unsigned char*)hash.data, &ctx)) + return false; + return true; + } } diff --git a/src/common/util.h b/src/common/util.h index c2ffc44ca..8ab469129 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -183,4 +183,7 @@ namespace tools unsigned get_max_concurrency(); bool is_local_address(const std::string &address); + int vercmp(const char *v0, const char *v1); // returns < 0, 0, > 0, similar to strcmp, but more human friendly than lexical - does not attempt to validate + + bool sha256sum(const std::string &filename, crypto::hash &hash); } |