You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
7.4 KiB
263 lines
7.4 KiB
// |
|
// Alert system |
|
// |
|
|
|
#include <algorithm> |
|
#include <boost/algorithm/string/classification.hpp> |
|
#include <boost/algorithm/string/replace.hpp> |
|
#include <boost/foreach.hpp> |
|
#include <map> |
|
|
|
#include "alert.h" |
|
#include "key.h" |
|
#include "net.h" |
|
#include "sync.h" |
|
#include "ui_interface.h" |
|
|
|
using namespace std; |
|
|
|
map<uint256, CAlert> mapAlerts; |
|
CCriticalSection cs_mapAlerts; |
|
|
|
void CUnsignedAlert::SetNull() |
|
{ |
|
nVersion = 1; |
|
nRelayUntil = 0; |
|
nExpiration = 0; |
|
nID = 0; |
|
nCancel = 0; |
|
setCancel.clear(); |
|
nMinVer = 0; |
|
nMaxVer = 0; |
|
setSubVer.clear(); |
|
nPriority = 0; |
|
|
|
strComment.clear(); |
|
strStatusBar.clear(); |
|
strReserved.clear(); |
|
} |
|
|
|
std::string CUnsignedAlert::ToString() const |
|
{ |
|
std::string strSetCancel; |
|
BOOST_FOREACH(int n, setCancel) |
|
strSetCancel += strprintf("%d ", n); |
|
std::string strSetSubVer; |
|
BOOST_FOREACH(std::string str, setSubVer) |
|
strSetSubVer += "\"" + str + "\" "; |
|
return strprintf( |
|
"CAlert(\n" |
|
" nVersion = %d\n" |
|
" nRelayUntil = %"PRI64d"\n" |
|
" nExpiration = %"PRI64d"\n" |
|
" nID = %d\n" |
|
" nCancel = %d\n" |
|
" setCancel = %s\n" |
|
" nMinVer = %d\n" |
|
" nMaxVer = %d\n" |
|
" setSubVer = %s\n" |
|
" nPriority = %d\n" |
|
" strComment = \"%s\"\n" |
|
" strStatusBar = \"%s\"\n" |
|
")\n", |
|
nVersion, |
|
nRelayUntil, |
|
nExpiration, |
|
nID, |
|
nCancel, |
|
strSetCancel.c_str(), |
|
nMinVer, |
|
nMaxVer, |
|
strSetSubVer.c_str(), |
|
nPriority, |
|
strComment.c_str(), |
|
strStatusBar.c_str()); |
|
} |
|
|
|
void CUnsignedAlert::print() const |
|
{ |
|
LogPrintf("%s", ToString().c_str()); |
|
} |
|
|
|
void CAlert::SetNull() |
|
{ |
|
CUnsignedAlert::SetNull(); |
|
vchMsg.clear(); |
|
vchSig.clear(); |
|
} |
|
|
|
bool CAlert::IsNull() const |
|
{ |
|
return (nExpiration == 0); |
|
} |
|
|
|
uint256 CAlert::GetHash() const |
|
{ |
|
return Hash(this->vchMsg.begin(), this->vchMsg.end()); |
|
} |
|
|
|
bool CAlert::IsInEffect() const |
|
{ |
|
return (GetAdjustedTime() < nExpiration); |
|
} |
|
|
|
bool CAlert::Cancels(const CAlert& alert) const |
|
{ |
|
if (!IsInEffect()) |
|
return false; // this was a no-op before 31403 |
|
return (alert.nID <= nCancel || setCancel.count(alert.nID)); |
|
} |
|
|
|
bool CAlert::AppliesTo(int nVersion, std::string strSubVerIn) const |
|
{ |
|
// TODO: rework for client-version-embedded-in-strSubVer ? |
|
return (IsInEffect() && |
|
nMinVer <= nVersion && nVersion <= nMaxVer && |
|
(setSubVer.empty() || setSubVer.count(strSubVerIn))); |
|
} |
|
|
|
bool CAlert::AppliesToMe() const |
|
{ |
|
return AppliesTo(PROTOCOL_VERSION, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector<std::string>())); |
|
} |
|
|
|
bool CAlert::RelayTo(CNode* pnode) const |
|
{ |
|
if (!IsInEffect()) |
|
return false; |
|
// returns true if wasn't already contained in the set |
|
if (pnode->setKnown.insert(GetHash()).second) |
|
{ |
|
if (AppliesTo(pnode->nVersion, pnode->strSubVer) || |
|
AppliesToMe() || |
|
GetAdjustedTime() < nRelayUntil) |
|
{ |
|
pnode->PushMessage("alert", *this); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
bool CAlert::CheckSignature() const |
|
{ |
|
CPubKey key(Params().AlertKey()); |
|
if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig)) |
|
return error("CAlert::CheckSignature() : verify signature failed"); |
|
|
|
// Now unserialize the data |
|
CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION); |
|
sMsg >> *(CUnsignedAlert*)this; |
|
return true; |
|
} |
|
|
|
CAlert CAlert::getAlertByHash(const uint256 &hash) |
|
{ |
|
CAlert retval; |
|
{ |
|
LOCK(cs_mapAlerts); |
|
map<uint256, CAlert>::iterator mi = mapAlerts.find(hash); |
|
if(mi != mapAlerts.end()) |
|
retval = mi->second; |
|
} |
|
return retval; |
|
} |
|
|
|
bool CAlert::ProcessAlert(bool fThread) |
|
{ |
|
if (!CheckSignature()) |
|
return false; |
|
if (!IsInEffect()) |
|
return false; |
|
|
|
// alert.nID=max is reserved for if the alert key is |
|
// compromised. It must have a pre-defined message, |
|
// must never expire, must apply to all versions, |
|
// and must cancel all previous |
|
// alerts or it will be ignored (so an attacker can't |
|
// send an "everything is OK, don't panic" version that |
|
// cannot be overridden): |
|
int maxInt = std::numeric_limits<int>::max(); |
|
if (nID == maxInt) |
|
{ |
|
if (!( |
|
nExpiration == maxInt && |
|
nCancel == (maxInt-1) && |
|
nMinVer == 0 && |
|
nMaxVer == maxInt && |
|
setSubVer.empty() && |
|
nPriority == maxInt && |
|
strStatusBar == "URGENT: Alert key compromised, upgrade required" |
|
)) |
|
return false; |
|
} |
|
|
|
{ |
|
LOCK(cs_mapAlerts); |
|
// Cancel previous alerts |
|
for (map<uint256, CAlert>::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();) |
|
{ |
|
const CAlert& alert = (*mi).second; |
|
if (Cancels(alert)) |
|
{ |
|
LogPrint("alert", "cancelling alert %d\n", alert.nID); |
|
uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); |
|
mapAlerts.erase(mi++); |
|
} |
|
else if (!alert.IsInEffect()) |
|
{ |
|
LogPrint("alert", "expiring alert %d\n", alert.nID); |
|
uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); |
|
mapAlerts.erase(mi++); |
|
} |
|
else |
|
mi++; |
|
} |
|
|
|
// Check if this alert has been cancelled |
|
BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts) |
|
{ |
|
const CAlert& alert = item.second; |
|
if (alert.Cancels(*this)) |
|
{ |
|
LogPrint("alert", "alert already cancelled by %d\n", alert.nID); |
|
return false; |
|
} |
|
} |
|
|
|
// Add to mapAlerts |
|
mapAlerts.insert(make_pair(GetHash(), *this)); |
|
// Notify UI and -alertnotify if it applies to me |
|
if(AppliesToMe()) |
|
{ |
|
uiInterface.NotifyAlertChanged(GetHash(), CT_NEW); |
|
std::string strCmd = GetArg("-alertnotify", ""); |
|
if (!strCmd.empty()) |
|
{ |
|
// Alert text should be plain ascii coming from a trusted source, but to |
|
// be safe we first strip anything not in safeChars, then add single quotes around |
|
// the whole string before passing it to the shell: |
|
std::string singleQuote("'"); |
|
// safeChars chosen to allow simple messages/URLs/email addresses, but avoid anything |
|
// even possibly remotely dangerous like & or > |
|
std::string safeChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890 .,;_/:?@"); |
|
std::string safeStatus; |
|
for (std::string::size_type i = 0; i < strStatusBar.size(); i++) |
|
{ |
|
if (safeChars.find(strStatusBar[i]) != std::string::npos) |
|
safeStatus.push_back(strStatusBar[i]); |
|
} |
|
safeStatus = singleQuote+safeStatus+singleQuote; |
|
boost::replace_all(strCmd, "%s", safeStatus); |
|
|
|
if (fThread) |
|
boost::thread t(runCommand, strCmd); // thread runs free |
|
else |
|
runCommand(strCmd); |
|
} |
|
} |
|
} |
|
|
|
LogPrint("alert", "accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); |
|
return true; |
|
}
|
|
|