// // Fulcrum - A fast & nimble SPV Server for Bitcoin Cash // Copyright (C) 2019-2020 Calin A. Culianu // // This program 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. // // This program 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 this program (see LICENSE.txt). If not, see // . // #include "Options.h" #include #include #include /* static */ void Options::test() { // paranoia static_assert ( maxHistoryMin > 0 && maxHistoryMax > 0 && maxBufferMin > 0 && maxBufferMax > 0 && defaultMaxBuffer > 0 && defaultMaxHistory > 0, "maxHistory and/or maxBuffer may not be <= 0" ); } /*static*/ auto Options::Subnet::fromString(const QString &s) -> Subnet { Subnet sn; const auto pair = QHostAddress::parseSubnet(s.trimmed()); sn.subnet = pair.first; sn.mask = pair.second; return sn; } QString Options::Subnet::toString() const { QString ret; if (!isValid()) ret = ""; else ret = QString("%1/%2").arg(subnet.toString()).arg(mask); return ret; } bool Options::isAddrInPerIPLimitExcludeSet(const QHostAddress &addr, Subnet *matched) const { // linearly search through excluded subnets -- this is normally only called if some limit was hit (which is rare) // so it should hopefully be fast enough. for (const auto & sn : subnetsExcludedFromPerIPLimits) { if (addr.isInSubnet(sn.subnet, sn.mask)) { if (matched) *matched = sn; return true; } } return false; } QVariantMap Options::toMap() const { QVariantMap m; m["debug"] = verboseDebug.load(); m["trace"] = verboseTrace.load(); QVariantList l; for (const auto & pair : interfaces) l.push_back(QString("%1:%2").arg(pair.first.toString()).arg(pair.second)); m["tcp"] = l; l.clear(); for (const auto & pair : sslInterfaces) l.push_back(QString("%1:%2").arg(pair.first.toString()).arg(pair.second)); m["ssl"] = l; l.clear(); for (const auto & pair : statsInterfaces) l.push_back(QString("%1:%2").arg(pair.first.toString()).arg(pair.second)); m["stats"] = l; m["cert"] = certFile; m["key"] = keyFile; m["bitcoind"] = QString("%1:%2").arg(bitcoind.first.toString()).arg(bitcoind.second); m["rpcuser"] = rpcuser.isNull() ? QVariant() : QVariant(""); m["rpcpassword"] = rpcpassword.isNull() ? QVariant() : QVariant(""); m["datadir"] = datadir; m["checkdb"] = doSlowDbChecks; m["polltime"] = pollTimeSecs; m["donation"] = donationAddress; m["banner"] = bannerFile; m["peering"] = peerDiscovery; m["peering_announce_self"] = peerAnnounceSelf; m["hostname"] = hostName.has_value() ? QVariant(hostName.value()) : QVariant(); m["public_tcp"] = publicTcp.has_value() ? QVariant(publicTcp.value()) : QVariant(); m["public_ssl"] = publicSsl.has_value() ? QVariant(publicSsl.value()) : QVariant(); m["max_clients_per_ip"] = maxClientsPerIP; l.clear(); for (const auto & sn : subnetsExcludedFromPerIPLimits) l.push_back(sn.toString()); m["subnets_to_exclude_from_per_ip_limits"] = l; m["max_buffer"] = maxBuffer; m["max_history"] = maxHistory; m["workqueue"] = workQueue; m["worker_threads"] = workerThreads; return m; } // -- ConfigFile std::optional ConfigFile::optValue(const QString &name, Qt::CaseSensitivity cs) const { std::optional ret; if (cs == Qt::CaseSensitive) { if (auto it = map.find(name); it != map.end()) ret = it.value(); } else { for (auto it = map.begin(); it != map.end(); ++it) { if (it.key().compare(name, cs) == 0) { ret = it.value(); break; } } } return ret; } int ConfigFile::remove(const QString &name, Qt::CaseSensitivity cs) { if (cs == Qt::CaseSensitive) { return map.remove(name); } else { int ctr = 0; for (auto it = map.begin(), next = it; it != map.end(); it = next) { if (it.key().compare(name, cs) == 0) { next = map.erase(it); ++ctr; } else ++next; } return ctr; } } bool ConfigFile::hasValue(const QString &name, Qt::CaseSensitivity cs) const { return optValue(name, cs).has_value(); } QString ConfigFile::value(const QString &name, const QString & def, Qt::CaseSensitivity cs) const { return optValue(name, cs).value_or(def); } QStringList ConfigFile::values(const QString &name, Qt::CaseSensitivity cs) const { QStringList ret; if (cs == Qt::CaseSensitive) { ret = map.values(name); } else { for (auto it = map.begin(); it != map.end(); ++it) { if (it.key().compare(name, cs) == 0) ret.push_back(it.value()); } } return ret; } bool ConfigFile::open(const QString &filePath) { static const QChar comment('#'), eq('='), bracket('['); clear(); QList lines; { QFile f(filePath); if (!f.exists() || !f.open(QIODevice::ReadOnly|QIODevice::Text)) return false; auto fileData = f.read(1024*1024*1); // read up to 1 MB if (fileData.isEmpty()) return f.error() == QFile::FileError::NoError; lines = fileData.split('\n'); } for (const auto & lineData : lines) { QString line = QString::fromUtf8(lineData).trimmed(); if (!line.isEmpty() && line.at(0) == bracket) continue; // ignore "[section]" headers in case the user thinks we support these if (const int cpos = line.indexOf(comment); cpos > -1) // find and throw away comments (everything after '#' char) line = line.left(cpos).trimmed(); if (line.isEmpty()) continue; QString name, value; if (const int eqpos = line.indexOf(eq); eqpos > -1) { name = line.left(eqpos).trimmed(); if (name.isEmpty()) continue; value = line.mid(eqpos+1).trimmed(); } else // a name by itself on a line with no equal sign, becomes name=(emptystring), which might be useful to // indicate the name was present (can act like a bool flag if present) name = line; // save item map.insertMulti(name, value); } map.squeeze(); return true; } bool ConfigFile::boolValue(const QString & name, bool def, bool *parsedOk, Qt::CaseSensitivity cs) const { bool dummy; bool & ok ( parsedOk ? *parsedOk : dummy ); ok = false; const auto opt = optValue(name, cs); if (!opt.has_value()) return def; const auto val = opt.value().toLower(); if (val == "true" || val == "yes" || val == "on" || val.isEmpty() /* "" means true! */) { ok = true; return true; } else if (val == "false" || val == "no" || val == "off") { ok = true; return false; } bool ret = val.toInt(&ok); if (!ok) ret = def; return ret; } int ConfigFile::intValue(const QString & name, int def, bool *parsedOk, Qt::CaseSensitivity cs) const { bool dummy; bool & ok ( parsedOk ? *parsedOk : dummy ); ok = false; const auto opt = optValue(name, cs); if (!opt.has_value()) return def; const auto val = opt.value().toLower(); int ret = val.toInt(&ok); if (!ok) ret = def; return ret; } double ConfigFile::doubleValue(const QString & name, double def, bool *parsedOk, Qt::CaseSensitivity cs) const { bool dummy; bool & ok ( parsedOk ? *parsedOk : dummy ); ok = false; const auto opt = optValue(name, cs); if (!opt.has_value()) return def; const auto val = opt.value().toLower(); double ret = val.toDouble(&ok); if (!ok) ret = def; return ret; }