mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-05 19:25:49 +00:00
Compare commits
1 Commits
sync-2.0.1
...
feature-em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
256f84b4de |
493
src/ripple/protocol/Email.h
Normal file
493
src/ripple/protocol/Email.h
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// make this typedef to keep dkim happy
|
||||||
|
typedef int _Bool;
|
||||||
|
#include <opendkim/dkim.h>
|
||||||
|
|
||||||
|
using namespace ripple;
|
||||||
|
namespace Email
|
||||||
|
{
|
||||||
|
|
||||||
|
enum EmailType : uint8_t
|
||||||
|
{
|
||||||
|
INVALID = 0,
|
||||||
|
REMIT = 1,
|
||||||
|
REKEY = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EmailDetails
|
||||||
|
{
|
||||||
|
std::string domain; // from address domain
|
||||||
|
std::string dkimDomain; // dkim signature domain
|
||||||
|
|
||||||
|
AccountID from;
|
||||||
|
std::string fromEmail;
|
||||||
|
|
||||||
|
std::optional<std::string> toEmail;
|
||||||
|
std::optional<AccountID> to;
|
||||||
|
|
||||||
|
EmailType emailType { EmailType::INVALID };
|
||||||
|
std::optional<STAmount> amount; // only valid if REMIT type
|
||||||
|
std::optional<AccountID> rekey; // only valid if REKEY type
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpenDKIM
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
DKIM_STAT status;
|
||||||
|
public:
|
||||||
|
DKIM_LIB* dkim_lib;
|
||||||
|
DKIM* dkim;
|
||||||
|
|
||||||
|
bool sane()
|
||||||
|
{
|
||||||
|
return !!dkim_lib && !!dkim;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenDKIM()
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup is in its own function not the constructor to make failure graceful
|
||||||
|
bool setup(beast::Journal& j)
|
||||||
|
{
|
||||||
|
dkim_lib = dkim_init(nullptr, nullptr);
|
||||||
|
if (!dkim_lib)
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Failed to init dkim_lib.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DKIM_STAT status;
|
||||||
|
DKIM* dkim = dkim_verify(dkim_lib, (uint8_t const*)"id", nullptr, &status);
|
||||||
|
if (!dkim_lib)
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Failed to init dkim_verify.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
~OpenDKIM()
|
||||||
|
{
|
||||||
|
if (dkim)
|
||||||
|
{
|
||||||
|
dkim_free(dkim);
|
||||||
|
dkim = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dkim_lib)
|
||||||
|
{
|
||||||
|
dkim_close(dkim_lib);
|
||||||
|
dkim_lib = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::optional<std::pair<std::string /* canonical email addr */, std::string /* canonical domain */>>
|
||||||
|
canonicalizeEmailAddress(const std::string& rawEmailAddr)
|
||||||
|
{
|
||||||
|
if (rawEmailAddr.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// trim
|
||||||
|
auto start = std::find_if_not(str.begin(), str.end(), ::isspace);
|
||||||
|
auto end = std::find_if_not(str.rbegin(), str.rend(), ::isspace).base();
|
||||||
|
|
||||||
|
if (end >= start)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::email = std::string(start, end);
|
||||||
|
|
||||||
|
if (email.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// to lower
|
||||||
|
std::transform(email.begin(), email.end(), email.begin(), ::tolower);
|
||||||
|
|
||||||
|
// find the @
|
||||||
|
size_t atPos = email.find('@');
|
||||||
|
if (atPos == std::string::npos || atPos == email.size() - 1)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string localPart = email.substr(0, atPos);
|
||||||
|
std::string domain = email.substr(atPos + 1);
|
||||||
|
|
||||||
|
if (domain.empty() || localPart.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// ensure there's only one @
|
||||||
|
if (domain.find('@') != std::string::npos)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// canonicalize domain part
|
||||||
|
{
|
||||||
|
std::string result = domain;
|
||||||
|
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
|
||||||
|
while (!result.empty() && result.back() == '.')
|
||||||
|
result.pop_back();
|
||||||
|
|
||||||
|
doamin = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domain.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// canonicalize local part
|
||||||
|
{
|
||||||
|
std::string part = localPart;
|
||||||
|
part.erase(std::remove_if(
|
||||||
|
part.begin(), part.end(),
|
||||||
|
[](char c) { return c == '(' || c == ')' || std::isspace(c); }), part.end());
|
||||||
|
|
||||||
|
size_t plusPos = part.find('+');
|
||||||
|
if (plusPos != std::string::npos)
|
||||||
|
part = part.substr(0, plusPos);
|
||||||
|
|
||||||
|
while (!part.empty() && part.back() == '.')
|
||||||
|
part.pop_back();
|
||||||
|
|
||||||
|
// gmail ignores dots
|
||||||
|
if (domain == "gmail.com")
|
||||||
|
part.erase(std::remove(part.begin(), part.end(), '.'), part.end());
|
||||||
|
|
||||||
|
localPart = part;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localPart.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return {{localPart + "@" + domain, domain}};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Warning: must supply already canonicalzied email
|
||||||
|
inline
|
||||||
|
std::optional<AccountID>
|
||||||
|
emailToAccountID(const std::string& canonicalEmail)
|
||||||
|
{
|
||||||
|
uint8_t innerHash[SHA512_DIGEST_LENGTH + 4];
|
||||||
|
SHA512_CTX sha512;
|
||||||
|
SHA512_Init(&sha512);
|
||||||
|
SHA512_Update(&sha512, canonicalEmail.c_str(), canonicalEmail.size());
|
||||||
|
SHA512_Final(innerHash + 4, &sha512);
|
||||||
|
innerHash[0] = 0xEEU;
|
||||||
|
innerHash[1] = 0xEEU;
|
||||||
|
innerHash[2] = 0xFFU;
|
||||||
|
innerHash[3] = 0xFFU;
|
||||||
|
{
|
||||||
|
uint8_t hash[SHA512_DIGEST_LENGTH];
|
||||||
|
SHA512_CTX sha512;
|
||||||
|
SHA512_Init(&sha512);
|
||||||
|
SHA512_Update(&sha512, innerHash, sizeof(innerHash));
|
||||||
|
SHA512_Final(hash, &sha512);
|
||||||
|
|
||||||
|
return AccountID::fromVoid((void*)hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::optional<EmailDetails>
|
||||||
|
parseEmail(std::string const& rawEmail, beast::Journal& j)
|
||||||
|
{
|
||||||
|
EmailDetails out;
|
||||||
|
|
||||||
|
// parse email into headers and body
|
||||||
|
std::vector<std::string> headers;
|
||||||
|
std::string body;
|
||||||
|
{
|
||||||
|
std::istringstream stream(rawEmail);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(stream, line))
|
||||||
|
{
|
||||||
|
if (line.empty() || line == "\r")
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Handle header line continuations
|
||||||
|
while (stream.peek() == ' ' || stream.peek() == '\t') {
|
||||||
|
std::string continuation;
|
||||||
|
std::getline(stream, continuation);
|
||||||
|
line += '\n' + continuation;
|
||||||
|
}
|
||||||
|
if (!line.empty()) {
|
||||||
|
headers.push_back(line.substr(0, line.size() - (line.back() == '\r' ? 1 : 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream body_stream;
|
||||||
|
while (std::getline(stream, line))
|
||||||
|
body_stream << line << "\n";
|
||||||
|
body = body_stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// find the from address, canonicalize it and extract the domain
|
||||||
|
bool foundFrom = false;
|
||||||
|
bool foundTo = false;
|
||||||
|
{
|
||||||
|
static const std::regex
|
||||||
|
from_regex(R"(^From:\s*(?:.*<)?([^<>\s]+@[^<>\s]+)(?:>)?)", std::regex::icase);
|
||||||
|
|
||||||
|
static const std::regex
|
||||||
|
to_regex(R"(^To:\s*(?:.*<)?([^<>\s]+@[^<>\s]+)(?:>)?)", std::regex::icase);
|
||||||
|
|
||||||
|
for (const auto& header : headers)
|
||||||
|
{
|
||||||
|
if (foundFrom && foundTo)
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
if (!foundFrom && std::regex_search(header, match, from_regex) && match.size() > 1)
|
||||||
|
{
|
||||||
|
auto canon = canonicalizeEmailAddress(match[1].str());
|
||||||
|
if (!canon)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: Cannot parse From address: `"
|
||||||
|
<< match[1].str() << "`";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.fromEmail = canon->first;
|
||||||
|
out.domain = canon->second;
|
||||||
|
out.from = emailToAccountID(out.fromEmail);
|
||||||
|
foundFrom = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::regex_search(header, match, to_regex) && match.size() > 1)
|
||||||
|
{
|
||||||
|
auto canon = canonicalizeEmailAddress(match[1].str());
|
||||||
|
if (!canon)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: Cannot parse To address: `"
|
||||||
|
<< match[1].str() << "`";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.toEmail = canon->first;
|
||||||
|
out.to = emailToAccountID(out.toEmail);
|
||||||
|
|
||||||
|
foundTo = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundFrom)
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: No From address present in email.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execution to here means we have:
|
||||||
|
// 1. Parsed headers and body
|
||||||
|
// 2. Found a from address and canonicalzied it
|
||||||
|
// 3. Potentially found a to address and canonicalized it.
|
||||||
|
|
||||||
|
// Find instructions
|
||||||
|
{
|
||||||
|
static const std::regex
|
||||||
|
remitPattern(R"(^REMIT (\d+(?:\.\d+)?) ([A-Z]{3})(?:/([r][a-zA-Z0-9]{24,34}))?)");
|
||||||
|
|
||||||
|
static const std::regex
|
||||||
|
rekeyPattern(R"(^REKEY ([r][a-zA-Z0-9]{24,34}))");
|
||||||
|
|
||||||
|
std::istringstream stream(body);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
out.emailType = EmailType::INVALID;
|
||||||
|
|
||||||
|
while (std::getline(stream, line, '\n'))
|
||||||
|
{
|
||||||
|
if (!line.empty() && line.back() == '\r')
|
||||||
|
line.pop_back(); // Remove '\r' if present
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
if (std::regex_match(line, match, remitPattern))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Currency cur;
|
||||||
|
if (!to_currency(cur, match[2]))
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Could not parse currency code.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AccountID issuer = noAccount();
|
||||||
|
if (match[3].matched)
|
||||||
|
{
|
||||||
|
if (isXRP(cur))
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Native currency cannot specify issuer.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
issuer = decodeBase58Token(match[3], TokenType::AccountID);
|
||||||
|
if (issuer.empty())
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Could not parse issuer address.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.amount = amountFromString({cur, issuer}, match[1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Exception while parsing REMIT. " << e.what();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.emailType = EmailType::REMIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::regex_match(line, match, rekeyPattern))
|
||||||
|
{
|
||||||
|
AccountID rekey = decodeBase58Token(match[1], TokenType::AccountID);
|
||||||
|
if (rekey.empty())
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Could not parse rekey address.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.rekey = rekey;
|
||||||
|
out.emailType = EmailType::REKEY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out.emailType == EmailType::INVALID)
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "EmailAmendment: Invalid email type, could not find REMIT or REKEY.";
|
||||||
|
return{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// perform DKIM checks...
|
||||||
|
// to do this we will use OpenDKIM, and manage it with a smart pointer to prevent
|
||||||
|
// any leaks from uncommon exit pathways
|
||||||
|
|
||||||
|
std::unique<OpenDKIM> odkim;
|
||||||
|
|
||||||
|
// perform setup
|
||||||
|
if (!odkim->setup(j) || !odkim->sane())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// when odkim goes out of scope it will call the C-apis to destroy the dkim instances
|
||||||
|
|
||||||
|
DKIM_STAT status;
|
||||||
|
DKIM_LIB* dkim_lib = odkim->dkim_lib;
|
||||||
|
DKIM* dkim = odkim->dkim;
|
||||||
|
|
||||||
|
// feed opendkim all headers
|
||||||
|
{
|
||||||
|
for (const auto& header : headers)
|
||||||
|
{
|
||||||
|
status = dkim_header(dkim, (uint8_t*)header.c_str(), header.length());
|
||||||
|
if (status != DKIM_STAT_OK)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: OpenDKIM Failed to process header: "
|
||||||
|
<< dkim_geterror(dkim);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = dkim_eoh(dkim);
|
||||||
|
if (status != DKIM_STAT_OK)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: OpenDKIM Failed to send end-of-headers"l
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// feed opendkim email body
|
||||||
|
{
|
||||||
|
status = dkim_body(dkim, (uint8_t*)body.c_str(), body.size());
|
||||||
|
if (status != DKIM_STAT_OK)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: OpenDKIM Failed to process body: "
|
||||||
|
<< dkim_geterror(dkim);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_Bool testkey;
|
||||||
|
status = dkim_eom(dkim, &testkey);
|
||||||
|
if (status != DKIM_STAT_OK)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: OpenDKIM end-of-message error: "
|
||||||
|
<< dkim_geterror(dkim);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
DKIM_SIGINFO* sig = dkim_getsignature(dkim);
|
||||||
|
if (!sig)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: No DKIM signature found";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dkim_sig_getbh(sig) != DKIM_SIGBH_MATCH)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: DKIM body hash mismatch";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DKIM_SIGINFO* sig = dkim_getsignature(dkim);
|
||||||
|
if (!sig)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: DKIM signature not found.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.dkimDomain =
|
||||||
|
std::string(reinterpret_cast<char const*>(
|
||||||
|
reinterpret_cast<void const*>(dkim_sig_getdomain(sig))));
|
||||||
|
|
||||||
|
if (out.dkimDomain.empty())
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: DKIM signature domain empty.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// RH TODO: decide whether to relax this or not
|
||||||
|
// strict domain check
|
||||||
|
if (out.dkimDomain != out.domain)
|
||||||
|
{
|
||||||
|
JLOG(j.warn())
|
||||||
|
<< "EmailAmendment: DKIM domain does not match From address domain.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execution to here means all checks passed and the instruction was correctly parsed
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ namespace detail {
|
|||||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||||
// the actual number of amendments. A LogicError on startup will verify this.
|
// the actual number of amendments. A LogicError on startup will verify this.
|
||||||
static constexpr std::size_t numFeatures = 70;
|
static constexpr std::size_t numFeatures = 71;
|
||||||
|
|
||||||
/** Amendments that this server supports and the default voting behavior.
|
/** Amendments that this server supports and the default voting behavior.
|
||||||
Whether they are enabled depends on the Rules defined in the validated
|
Whether they are enabled depends on the Rules defined in the validated
|
||||||
@@ -358,6 +358,7 @@ extern uint256 const fixXahauV2;
|
|||||||
extern uint256 const featureRemit;
|
extern uint256 const featureRemit;
|
||||||
extern uint256 const featureZeroB2M;
|
extern uint256 const featureZeroB2M;
|
||||||
extern uint256 const fixNSDelete;
|
extern uint256 const fixNSDelete;
|
||||||
|
extern uint256 const featureEmail;
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <ripple/protocol/TxFormats.h>
|
#include <ripple/protocol/TxFormats.h>
|
||||||
#include <boost/container/flat_set.hpp>
|
#include <boost/container/flat_set.hpp>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <ripple/protocol/Email.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ namespace ripple {
|
|||||||
// Universal Transaction flags:
|
// Universal Transaction flags:
|
||||||
enum UniversalFlags : uint32_t {
|
enum UniversalFlags : uint32_t {
|
||||||
tfFullyCanonicalSig = 0x80000000,
|
tfFullyCanonicalSig = 0x80000000,
|
||||||
|
tfEmailSig = 0x40000000,
|
||||||
};
|
};
|
||||||
constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig;
|
constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig | tfEmailSig;
|
||||||
constexpr std::uint32_t tfUniversalMask = ~tfUniversal;
|
constexpr std::uint32_t tfUniversalMask = ~tfUniversal;
|
||||||
|
|
||||||
// AccountSet flags:
|
// AccountSet flags:
|
||||||
|
|||||||
@@ -464,6 +464,7 @@ REGISTER_FIX (fixXahauV2, Supported::yes, VoteBehavior::De
|
|||||||
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
|
REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo);
|
||||||
REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo);
|
REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo);
|
||||||
REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo);
|
REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo);
|
||||||
|
REGISTER_FEATURE(Email, Supported::yes, VoteBehavior::DefaultNo);
|
||||||
|
|
||||||
// The following amendments are obsolete, but must remain supported
|
// The following amendments are obsolete, but must remain supported
|
||||||
// because they could potentially get enabled.
|
// because they could potentially get enabled.
|
||||||
|
|||||||
@@ -304,9 +304,60 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
|
|||||||
bool const isWildcardNetwork =
|
bool const isWildcardNetwork =
|
||||||
isFieldPresent(sfNetworkID) && getFieldU32(sfNetworkID) == 65535;
|
isFieldPresent(sfNetworkID) && getFieldU32(sfNetworkID) == 65535;
|
||||||
|
|
||||||
|
// email signature flag signals that the txn is authorized
|
||||||
|
// only by the presence of a DKIM signed email in memos[0]
|
||||||
|
|
||||||
|
bool const isEmailSig =
|
||||||
|
getFlags() & tfEmailSig;
|
||||||
|
|
||||||
|
|
||||||
bool validSig = false;
|
bool validSig = false;
|
||||||
|
do
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (isEmailSig)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!isFieldPresent(sfMemos))
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto const& memos = st.getFieldArray(sfMemos);
|
||||||
|
auto const& memo = memos[0];
|
||||||
|
auto memoObj = dynamic_cast<STObject const*>(&memo);
|
||||||
|
if (!memoObj || (memoObj->getFName() != sfMemo))
|
||||||
|
break;
|
||||||
|
|
||||||
|
bool emailValid = false;
|
||||||
|
|
||||||
|
for (auto const& memoElement : *memoObj)
|
||||||
|
{
|
||||||
|
auto const& name = memoElement.getFName();
|
||||||
|
|
||||||
|
if (name != sfMemoType && name != sfMemoData &&
|
||||||
|
name != sfMemoFormat)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// The raw data is stored as hex-octets, which we want to decode.
|
||||||
|
std::optional<Blob> optData = strUnHex(memoElement.getText());
|
||||||
|
|
||||||
|
if (!optData)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (name != sfMemoData)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string const emailContent((char const*)(optData->data()), optData->size());
|
||||||
|
|
||||||
|
|
||||||
|
// RH UPTO
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
|
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
|
||||||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
|
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
|
||||||
|
|
||||||
@@ -328,7 +379,8 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
|
|||||||
{
|
{
|
||||||
// Assume it was a signature failure.
|
// Assume it was a signature failure.
|
||||||
validSig = false;
|
validSig = false;
|
||||||
}
|
} while (0);
|
||||||
|
|
||||||
if (validSig == false)
|
if (validSig == false)
|
||||||
return Unexpected("Invalid signature.");
|
return Unexpected("Invalid signature.");
|
||||||
// Signature was verified.
|
// Signature was verified.
|
||||||
|
|||||||
Reference in New Issue
Block a user