429 lines
13 KiB
C++
429 lines
13 KiB
C++
/*
|
|
This file is part of progminer.
|
|
|
|
progminer is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
progminer is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with progminer. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <sstream>
|
|
|
|
#include <cstring>
|
|
|
|
#include <libpoolprotocols/PoolURI.h>
|
|
|
|
using namespace dev;
|
|
|
|
struct SchemeAttributes
|
|
{
|
|
ProtocolFamily family;
|
|
SecureLevel secure;
|
|
unsigned version;
|
|
};
|
|
|
|
static std::map<std::string, SchemeAttributes> s_schemes = {
|
|
/*
|
|
This schemes are kept for backwards compatibility.
|
|
Progminer do perform stratum autodetection
|
|
*/
|
|
{"stratum+tcp", {ProtocolFamily::STRATUM, SecureLevel::NONE, 0}},
|
|
{"stratum1+tcp", {ProtocolFamily::STRATUM, SecureLevel::NONE, 1}},
|
|
{"stratum2+tcp", {ProtocolFamily::STRATUM, SecureLevel::NONE, 2}},
|
|
{"stratum3+tcp", {ProtocolFamily::STRATUM, SecureLevel::NONE, 3}},
|
|
{"stratum+tls", {ProtocolFamily::STRATUM, SecureLevel::TLS, 0}},
|
|
{"stratum1+tls", {ProtocolFamily::STRATUM, SecureLevel::TLS, 1}},
|
|
{"stratum2+tls", {ProtocolFamily::STRATUM, SecureLevel::TLS, 2}},
|
|
{"stratum3+tls", {ProtocolFamily::STRATUM, SecureLevel::TLS, 3}},
|
|
{"stratum+tls12", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 0}},
|
|
{"stratum1+tls12", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 1}},
|
|
{"stratum2+tls12", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 2}},
|
|
{"stratum3+tls12", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 3}},
|
|
{"stratum+ssl", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 0}},
|
|
{"stratum1+ssl", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 1}},
|
|
{"stratum2+ssl", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 2}},
|
|
{"stratum3+ssl", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 3}},
|
|
{"http", {ProtocolFamily::GETWORK, SecureLevel::NONE, 0}},
|
|
{"getwork", {ProtocolFamily::GETWORK, SecureLevel::NONE, 0}},
|
|
|
|
/*
|
|
Any TCP scheme has, at the moment, only STRATUM protocol thus
|
|
reiterating "stratum" word would be pleonastic
|
|
Version 9 means auto-detect stratum mode
|
|
*/
|
|
|
|
{"stratum", {ProtocolFamily::STRATUM, SecureLevel::NONE, 999}},
|
|
{"stratums", {ProtocolFamily::STRATUM, SecureLevel::TLS, 999}},
|
|
{"stratumss", {ProtocolFamily::STRATUM, SecureLevel::TLS12, 999}},
|
|
|
|
/*
|
|
The following scheme is only meant for simulation operations
|
|
It's not meant to be used with -P arguments
|
|
*/
|
|
|
|
{"simulation", {ProtocolFamily::SIMULATION, SecureLevel::NONE, 999}}
|
|
};
|
|
|
|
static bool url_decode(const std::string& in, std::string& out)
|
|
{
|
|
out.clear();
|
|
out.reserve(in.size());
|
|
for (std::size_t i = 0; i < in.size(); ++i)
|
|
{
|
|
if (in[i] == '%')
|
|
{
|
|
if (i + 3 <= in.size())
|
|
{
|
|
int value = 0;
|
|
std::istringstream is(in.substr(i + 1, 2));
|
|
if (is >> std::hex >> value)
|
|
{
|
|
out += static_cast<char>(value);
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (in[i] == '+')
|
|
{
|
|
out += ' ';
|
|
}
|
|
else
|
|
{
|
|
out += in[i];
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
For a well designed explanation of URI parts
|
|
refer to https://cpp-netlib.org/0.10.1/in_depth/uri.html
|
|
*/
|
|
|
|
URI::URI(std::string uri, bool _sim) : m_uri{std::move(uri)}
|
|
{
|
|
|
|
std::regex sch_auth("^([a-zA-Z0-9\\+]{1,})\\:\\/\\/(.*)$");
|
|
std::smatch matches;
|
|
if (!std::regex_search(m_uri, matches, sch_auth, std::regex_constants::match_default))
|
|
return;
|
|
|
|
// Split scheme and authoority
|
|
// Authority MUST be valued
|
|
m_scheme = matches[1].str();
|
|
boost::algorithm::to_lower(m_scheme);
|
|
m_authority = matches[2].str();
|
|
|
|
// Missing authority is not possible
|
|
if (m_authority.empty())
|
|
throw std::runtime_error("Invalid authority");
|
|
|
|
// Simulation scheme is only allowed if specifically set
|
|
if (!_sim && m_scheme == "simulation")
|
|
throw std::runtime_error("Invalid scheme");
|
|
|
|
// Check scheme is allowed
|
|
if ((s_schemes.find(m_scheme) == s_schemes.end()))
|
|
throw std::runtime_error("Invalid scheme");
|
|
|
|
|
|
// Now let's see if authority part can be split into userinfo and "the rest"
|
|
std::regex usr_url("^(.*)\\@(.*)$");
|
|
if (std::regex_search(m_authority, matches, usr_url, std::regex_constants::match_default))
|
|
{
|
|
m_userinfo = matches[1].str();
|
|
m_urlinfo = matches[2].str();
|
|
}
|
|
else
|
|
{
|
|
m_urlinfo = m_authority;
|
|
}
|
|
|
|
/*
|
|
If m_userinfo present and valued it can be composed by either :
|
|
- user
|
|
- user.worker
|
|
- user.worker:password
|
|
- user:password
|
|
|
|
In other words . delimits the beginning of worker and : delimits
|
|
the beginning of password
|
|
|
|
*/
|
|
if (!m_userinfo.empty())
|
|
{
|
|
|
|
// Save all parts enclosed in backticks into a dictionary
|
|
// and replace them with tokens in the authority
|
|
std::regex btick("`((?:[^`])*)`");
|
|
std::map<std::string, std::string> btick_blocks;
|
|
auto btick_blocks_begin =
|
|
std::sregex_iterator(m_authority.begin(), m_authority.end(), btick);
|
|
auto btick_blocks_end = std::sregex_iterator();
|
|
int i = 0;
|
|
for (std::sregex_iterator it = btick_blocks_begin; it != btick_blocks_end; ++it)
|
|
{
|
|
std::smatch match = *it;
|
|
std::string match_str = match[1].str();
|
|
btick_blocks["_" + std::to_string(i++)] = match[1].str();
|
|
}
|
|
if (btick_blocks.size())
|
|
{
|
|
std::map<std::string, std::string>::iterator it;
|
|
for (it = btick_blocks.begin(); it != btick_blocks.end(); it++)
|
|
boost::replace_all(m_userinfo, "`" + it->second + "`", "`" + it->first + "`");
|
|
}
|
|
|
|
std::vector<std::regex> usr_patterns;
|
|
usr_patterns.push_back(std::regex("^(.*)\\.(.*)\\:(.*)$"));
|
|
usr_patterns.push_back(std::regex("^(.*)\\:(.*)$"));
|
|
usr_patterns.push_back(std::regex("^(.*)\\.(.*)$"));
|
|
bool usrMatchFound = false;
|
|
for (size_t i = 0; i < usr_patterns.size() && !usrMatchFound; i++)
|
|
{
|
|
if (std::regex_search(
|
|
m_userinfo, matches, usr_patterns.at(i), std::regex_constants::match_default))
|
|
{
|
|
usrMatchFound = true;
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
m_user = matches[1].str();
|
|
m_worker = matches[2].str();
|
|
m_password = matches[3].str();
|
|
break;
|
|
case 1:
|
|
m_user = matches[1];
|
|
m_password = matches[2];
|
|
break;
|
|
case 2:
|
|
m_user = matches[1];
|
|
m_worker = matches[2];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// If no matches found after this loop it means all the user
|
|
// part is only user login
|
|
if (!usrMatchFound)
|
|
m_user = m_userinfo;
|
|
|
|
// Replace all tokens with their respective values
|
|
if (btick_blocks.size())
|
|
{
|
|
std::map<std::string, std::string>::iterator it;
|
|
for (it = btick_blocks.begin(); it != btick_blocks.end(); it++)
|
|
{
|
|
boost::replace_all(m_userinfo, "`" + it->first + "`", it->second);
|
|
boost::replace_all(m_user, "`" + it->first + "`", it->second);
|
|
boost::replace_all(m_worker, "`" + it->first + "`", it->second);
|
|
boost::replace_all(m_password, "`" + it->first + "`", it->second);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
Let's process the url part which must contain at least a host
|
|
an optional port and eventually a path (which may include a query
|
|
and a fragment)
|
|
Host can be a DNS host or an IP address.
|
|
Thus we can have
|
|
- host
|
|
- host/path
|
|
- host:port
|
|
- host:port/path
|
|
*/
|
|
size_t offset = m_urlinfo.find('/');
|
|
if (offset != std::string::npos)
|
|
{
|
|
m_hostinfo = m_urlinfo.substr(0, offset);
|
|
m_pathinfo = m_urlinfo.substr(offset);
|
|
}
|
|
else
|
|
{
|
|
m_hostinfo = m_urlinfo;
|
|
}
|
|
boost::algorithm::to_lower(m_hostinfo); // needed to ensure we properly hit "exit" as host
|
|
std::regex host_pattern("^(.*)\\:([0-9]{1,5})$");
|
|
if (std::regex_search(m_hostinfo, matches, host_pattern, std::regex_constants::match_default))
|
|
{
|
|
m_host = matches[1].str();
|
|
m_port = boost::lexical_cast<short>(matches[2].str());
|
|
}
|
|
else
|
|
{
|
|
m_host = m_hostinfo;
|
|
}
|
|
|
|
// Host info must be present and valued
|
|
if (m_host.empty())
|
|
throw std::runtime_error("Missing host");
|
|
|
|
/*
|
|
Eventually split path info into path query fragment
|
|
*/
|
|
if (!m_pathinfo.empty())
|
|
{
|
|
// Url Decode Path
|
|
|
|
std::vector<std::regex> path_patterns;
|
|
path_patterns.push_back(std::regex("(\\/.*)\\?(.*)\\#(.*)$"));
|
|
path_patterns.push_back(std::regex("(\\/.*)\\#(.*)$"));
|
|
path_patterns.push_back(std::regex("(\\/.*)\\?(.*)$"));
|
|
bool pathMatchFound = false;
|
|
for (size_t i = 0; i < path_patterns.size() && !pathMatchFound; i++)
|
|
{
|
|
if (std::regex_search(
|
|
m_pathinfo, matches, path_patterns.at(i), std::regex_constants::match_default))
|
|
{
|
|
pathMatchFound = true;
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
m_path = matches[1].str();
|
|
m_query = matches[2].str();
|
|
m_fragment = matches[3].str();
|
|
break;
|
|
case 1:
|
|
m_path = matches[1].str();
|
|
m_fragment = matches[2].str();
|
|
break;
|
|
case 2:
|
|
m_path = matches[1].str();
|
|
m_query = matches[2].str();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
// If no matches found after this loop it means all the pathinfo
|
|
// part is only path login
|
|
if (!pathMatchFound)
|
|
m_path = m_pathinfo;
|
|
|
|
}
|
|
}
|
|
|
|
// Determine host type
|
|
boost::system::error_code ec;
|
|
boost::asio::ip::address address = boost::asio::ip::address::from_string(m_host, ec);
|
|
if (!ec)
|
|
{
|
|
// This is a valid Ip Address
|
|
if (address.is_v4())
|
|
m_hostType = UriHostNameType::IPV4;
|
|
if (address.is_v6())
|
|
m_hostType = UriHostNameType::IPV6;
|
|
|
|
m_isLoopBack = address.is_loopback();
|
|
}
|
|
else
|
|
{
|
|
// Check if valid DNS hostname
|
|
std::regex hostNamePattern(
|
|
"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-"
|
|
"Za-z0-9\\-]*[A-Za-z0-9])$");
|
|
if (std::regex_match(m_host, hostNamePattern))
|
|
m_hostType = UriHostNameType::Dns;
|
|
else
|
|
m_hostType = UriHostNameType::Basic;
|
|
}
|
|
|
|
if (!m_user.empty())
|
|
boost::replace_all(m_user, "`", "");
|
|
if (!m_password.empty())
|
|
boost::replace_all(m_password, "`", "");
|
|
if (!m_worker.empty())
|
|
boost::replace_all(m_worker, "`", "");
|
|
|
|
// Eventually decode every encoded char
|
|
std::string tmpStr;
|
|
if (url_decode(m_userinfo, tmpStr))
|
|
m_userinfo = tmpStr;
|
|
if (url_decode(m_urlinfo, tmpStr))
|
|
m_urlinfo = tmpStr;
|
|
if (url_decode(m_hostinfo, tmpStr))
|
|
m_hostinfo = tmpStr;
|
|
if (url_decode(m_pathinfo, tmpStr))
|
|
m_pathinfo = tmpStr;
|
|
|
|
if (url_decode(m_path, tmpStr))
|
|
m_path = tmpStr;
|
|
if (url_decode(m_query, tmpStr))
|
|
m_query = tmpStr;
|
|
if (url_decode(m_fragment, tmpStr))
|
|
m_fragment = tmpStr;
|
|
if (url_decode(m_user, tmpStr))
|
|
m_user = tmpStr;
|
|
if (url_decode(m_password, tmpStr))
|
|
m_password = tmpStr;
|
|
if (url_decode(m_worker, tmpStr))
|
|
m_worker = tmpStr;
|
|
}
|
|
|
|
ProtocolFamily URI::Family() const
|
|
{
|
|
return s_schemes[m_scheme].family;
|
|
}
|
|
|
|
unsigned URI::Version() const
|
|
{
|
|
return s_schemes[m_scheme].version;
|
|
}
|
|
|
|
std::string URI::UserDotWorker() const
|
|
{
|
|
std::string _ret = m_user;
|
|
if (!m_worker.empty())
|
|
_ret.append("." + m_worker);
|
|
return _ret;
|
|
}
|
|
|
|
SecureLevel URI::SecLevel() const
|
|
{
|
|
return s_schemes[m_scheme].secure;
|
|
}
|
|
|
|
UriHostNameType URI::HostNameType() const
|
|
{
|
|
return m_hostType;
|
|
}
|
|
|
|
bool URI::IsLoopBack() const
|
|
{
|
|
return m_isLoopBack;
|
|
}
|
|
|
|
std::string URI::KnownSchemes(ProtocolFamily family)
|
|
{
|
|
std::string schemes;
|
|
for (const auto& s : s_schemes)
|
|
{
|
|
if ((s.second.family == family) && (s.second.version != 999))
|
|
schemes += s.first + " ";
|
|
}
|
|
return schemes;
|
|
}
|