1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2020 The Bitcoin Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #include <addrdb.h>
7 
8 #include <addrman.h>
9 #include <chainparams.h>
10 #include <clientversion.h>
11 #include <cstdint>
12 #include <hash.h>
13 #include <logging/timer.h>
14 #include <netbase.h>
15 #include <random.h>
16 #include <streams.h>
17 #include <tinyformat.h>
18 #include <univalue.h>
19 #include <util/settings.h>
20 #include <util/system.h>
21 
CBanEntry(const UniValue & json)22 CBanEntry::CBanEntry(const UniValue& json)
23     : nVersion(json["version"].get_int()), nCreateTime(json["ban_created"].get_int64()),
24       nBanUntil(json["banned_until"].get_int64())
25 {
26 }
27 
ToJson() const28 UniValue CBanEntry::ToJson() const
29 {
30     UniValue json(UniValue::VOBJ);
31     json.pushKV("version", nVersion);
32     json.pushKV("ban_created", nCreateTime);
33     json.pushKV("banned_until", nBanUntil);
34     return json;
35 }
36 
37 namespace {
38 
39 static const char* BANMAN_JSON_ADDR_KEY = "address";
40 
41 /**
42  * Convert a `banmap_t` object to a JSON array.
43  * @param[in] bans Bans list to convert.
44  * @return a JSON array, similar to the one returned by the `listbanned` RPC. Suitable for
45  * passing to `BanMapFromJson()`.
46  */
BanMapToJson(const banmap_t & bans)47 UniValue BanMapToJson(const banmap_t& bans)
48 {
49     UniValue bans_json(UniValue::VARR);
50     for (const auto& it : bans) {
51         const auto& address = it.first;
52         const auto& ban_entry = it.second;
53         UniValue j = ban_entry.ToJson();
54         j.pushKV(BANMAN_JSON_ADDR_KEY, address.ToString());
55         bans_json.push_back(j);
56     }
57     return bans_json;
58 }
59 
60 /**
61  * Convert a JSON array to a `banmap_t` object.
62  * @param[in] bans_json JSON to convert, must be as returned by `BanMapToJson()`.
63  * @param[out] bans Bans list to create from the JSON.
64  * @throws std::runtime_error if the JSON does not have the expected fields or they contain
65  * unparsable values.
66  */
BanMapFromJson(const UniValue & bans_json,banmap_t & bans)67 void BanMapFromJson(const UniValue& bans_json, banmap_t& bans)
68 {
69     for (const auto& ban_entry_json : bans_json.getValues()) {
70         CSubNet subnet;
71         const auto& subnet_str = ban_entry_json[BANMAN_JSON_ADDR_KEY].get_str();
72         if (!LookupSubNet(subnet_str, subnet)) {
73             throw std::runtime_error(
74                 strprintf("Cannot parse banned address or subnet: %s", subnet_str));
75         }
76         bans.insert_or_assign(subnet, CBanEntry{ban_entry_json});
77     }
78 }
79 
80 template <typename Stream, typename Data>
SerializeDB(Stream & stream,const Data & data)81 bool SerializeDB(Stream& stream, const Data& data)
82 {
83     // Write and commit header, data
84     try {
85         CHashWriter hasher(stream.GetType(), stream.GetVersion());
86         stream << Params().MessageStart() << data;
87         hasher << Params().MessageStart() << data;
88         stream << hasher.GetHash();
89     } catch (const std::exception& e) {
90         return error("%s: Serialize or I/O error - %s", __func__, e.what());
91     }
92 
93     return true;
94 }
95 
96 template <typename Data>
SerializeFileDB(const std::string & prefix,const fs::path & path,const Data & data,int version)97 bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version)
98 {
99     // Generate random temporary filename
100     uint16_t randv = 0;
101     GetRandBytes((unsigned char*)&randv, sizeof(randv));
102     std::string tmpfn = strprintf("%s.%04x", prefix, randv);
103 
104     // open temp output file, and associate with CAutoFile
105     fs::path pathTmp = gArgs.GetDataDirNet() / tmpfn;
106     FILE *file = fsbridge::fopen(pathTmp, "wb");
107     CAutoFile fileout(file, SER_DISK, version);
108     if (fileout.IsNull()) {
109         fileout.fclose();
110         remove(pathTmp);
111         return error("%s: Failed to open file %s", __func__, pathTmp.string());
112     }
113 
114     // Serialize
115     if (!SerializeDB(fileout, data)) {
116         fileout.fclose();
117         remove(pathTmp);
118         return false;
119     }
120     if (!FileCommit(fileout.Get())) {
121         fileout.fclose();
122         remove(pathTmp);
123         return error("%s: Failed to flush file %s", __func__, pathTmp.string());
124     }
125     fileout.fclose();
126 
127     // replace existing file, if any, with new file
128     if (!RenameOver(pathTmp, path)) {
129         remove(pathTmp);
130         return error("%s: Rename-into-place failed", __func__);
131     }
132 
133     return true;
134 }
135 
136 template <typename Stream, typename Data>
DeserializeDB(Stream & stream,Data & data,bool fCheckSum=true)137 bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true)
138 {
139     try {
140         CHashVerifier<Stream> verifier(&stream);
141         // de-serialize file header (network specific magic number) and ..
142         unsigned char pchMsgTmp[4];
143         verifier >> pchMsgTmp;
144         // ... verify the network matches ours
145         if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp)))
146             return error("%s: Invalid network magic number", __func__);
147 
148         // de-serialize data
149         verifier >> data;
150 
151         // verify checksum
152         if (fCheckSum) {
153             uint256 hashTmp;
154             stream >> hashTmp;
155             if (hashTmp != verifier.GetHash()) {
156                 return error("%s: Checksum mismatch, data corrupted", __func__);
157             }
158         }
159     }
160     catch (const std::exception& e) {
161         return error("%s: Deserialize or I/O error - %s", __func__, e.what());
162     }
163 
164     return true;
165 }
166 
167 template <typename Data>
DeserializeFileDB(const fs::path & path,Data & data,int version)168 bool DeserializeFileDB(const fs::path& path, Data& data, int version)
169 {
170     // open input file, and associate with CAutoFile
171     FILE* file = fsbridge::fopen(path, "rb");
172     CAutoFile filein(file, SER_DISK, version);
173     if (filein.IsNull()) {
174         LogPrintf("Missing or invalid file %s\n", path.string());
175         return false;
176     }
177     return DeserializeDB(filein, data);
178 }
179 } // namespace
180 
CBanDB(fs::path ban_list_path)181 CBanDB::CBanDB(fs::path ban_list_path)
182     : m_banlist_dat(ban_list_path.string() + ".dat"),
183       m_banlist_json(ban_list_path.string() + ".json")
184 {
185 }
186 
Write(const banmap_t & banSet)187 bool CBanDB::Write(const banmap_t& banSet)
188 {
189     std::vector<std::string> errors;
190     if (util::WriteSettings(m_banlist_json, {{JSON_KEY, BanMapToJson(banSet)}}, errors)) {
191         return true;
192     }
193 
194     for (const auto& err : errors) {
195         error("%s", err);
196     }
197     return false;
198 }
199 
Read(banmap_t & banSet,bool & dirty)200 bool CBanDB::Read(banmap_t& banSet, bool& dirty)
201 {
202     // If the JSON banlist does not exist, then try to read the non-upgraded banlist.dat.
203     if (!fs::exists(m_banlist_json)) {
204         // If this succeeds then we need to flush to disk in order to create the JSON banlist.
205         dirty = true;
206         return DeserializeFileDB(m_banlist_dat, banSet, CLIENT_VERSION);
207     }
208 
209     dirty = false;
210 
211     std::map<std::string, util::SettingsValue> settings;
212     std::vector<std::string> errors;
213 
214     if (!util::ReadSettings(m_banlist_json, settings, errors)) {
215         for (const auto& err : errors) {
216             LogPrintf("Cannot load banlist %s: %s\n", m_banlist_json.string(), err);
217         }
218         return false;
219     }
220 
221     try {
222         BanMapFromJson(settings[JSON_KEY], banSet);
223     } catch (const std::runtime_error& e) {
224         LogPrintf("Cannot parse banlist %s: %s\n", m_banlist_json.string(), e.what());
225         return false;
226     }
227 
228     return true;
229 }
230 
CAddrDB()231 CAddrDB::CAddrDB()
232 {
233     pathAddr = gArgs.GetDataDirNet() / "peers.dat";
234 }
235 
Write(const CAddrMan & addr)236 bool CAddrDB::Write(const CAddrMan& addr)
237 {
238     return SerializeFileDB("peers", pathAddr, addr, CLIENT_VERSION);
239 }
240 
Read(CAddrMan & addr)241 bool CAddrDB::Read(CAddrMan& addr)
242 {
243     return DeserializeFileDB(pathAddr, addr, CLIENT_VERSION);
244 }
245 
Read(CAddrMan & addr,CDataStream & ssPeers)246 bool CAddrDB::Read(CAddrMan& addr, CDataStream& ssPeers)
247 {
248     bool ret = DeserializeDB(ssPeers, addr, false);
249     if (!ret) {
250         // Ensure addrman is left in a clean state
251         addr.Clear();
252     }
253     return ret;
254 }
255 
DumpAnchors(const fs::path & anchors_db_path,const std::vector<CAddress> & anchors)256 void DumpAnchors(const fs::path& anchors_db_path, const std::vector<CAddress>& anchors)
257 {
258     LOG_TIME_SECONDS(strprintf("Flush %d outbound block-relay-only peer addresses to anchors.dat", anchors.size()));
259     SerializeFileDB("anchors", anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT);
260 }
261 
ReadAnchors(const fs::path & anchors_db_path)262 std::vector<CAddress> ReadAnchors(const fs::path& anchors_db_path)
263 {
264     std::vector<CAddress> anchors;
265     if (DeserializeFileDB(anchors_db_path, anchors, CLIENT_VERSION | ADDRV2_FORMAT)) {
266         LogPrintf("Loaded %i addresses from %s\n", anchors.size(), anchors_db_path.filename());
267     } else {
268         anchors.clear();
269     }
270 
271     fs::remove(anchors_db_path);
272     return anchors;
273 }
274