diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ef55447a0c..8d4868b030 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,7 @@ list( retroshare/rsevents.h retroshare/rsfiles.h retroshare/rsinit.h + retroshare/rsfriendrequest.h retroshare/rspeers.h ) list( @@ -417,6 +418,7 @@ list( rsserver/p3serverconfig.cc rsserver/rsloginhandler.cc rsserver/p3face-server.cc + rsserver/p3friendrequest.cc rsserver/p3peers.cc rsserver/rsaccounts.cc rsserver/rsinit.cc ) @@ -425,6 +427,7 @@ list( APPEND RS_IMPLEMENTATION_HEADERS rsserver/p3face.h rsserver/p3history.h + rsserver/p3friendrequest.h rsserver/p3peers.h rsserver/p3serverconfig.h rsserver/p3status.h diff --git a/src/libretroshare.pro b/src/libretroshare.pro index 812c593856..9269faacae 100644 --- a/src/libretroshare.pro +++ b/src/libretroshare.pro @@ -136,6 +136,7 @@ PUBLIC_HEADERS = retroshare/rsdisc.h \ retroshare/rsplugin.h \ retroshare/rschats.h \ retroshare/rsmail.h \ + retroshare/rsfriendrequest.h \ retroshare/rspeers.h \ retroshare/rsstatus.h \ retroshare/rsturtle.h \ @@ -442,6 +443,7 @@ HEADERS += pqi/authssl.h \ HEADERS += rsserver/p3face.h \ rsserver/p3history.h \ # rsserver/p3msgs.h \ + rsserver/p3friendrequest.h \ rsserver/p3peers.h \ rsserver/p3status.h \ rsserver/rsaccounts.h \ @@ -615,6 +617,7 @@ SOURCES += rsserver/p3face-config.cc \ rsserver/p3face-info.cc \ rsserver/p3history.cc \ # rsserver/p3msgs.cc \ + rsserver/p3friendrequest.cc \ rsserver/p3peers.cc \ rsserver/p3status.cc \ rsserver/rsinit.cc \ diff --git a/src/pqi/authssl.cc b/src/pqi/authssl.cc index 565315ea37..37f42ab526 100644 --- a/src/pqi/authssl.cc +++ b/src/pqi/authssl.cc @@ -44,6 +44,7 @@ #include "retroshare/rspeers.h" // for RsPeerDetails structure #include "retroshare/rsids.h" // for RsPeerDetails structure +#include "retroshare/rsfriendrequest.h" // record unknown-peer connection attempts #include "rsserver/p3face.h" /******************** notify of new Cert **************************/ @@ -1453,6 +1454,13 @@ int AuthSSLimpl::VerifyX509Callback(int /*preverify_ok*/, X509_STORE_CTX* ctx) //if (auth_diagnostic == RS_SSL_HANDSHAKE_DIAGNOSTIC_ISSUER_UNKNOWN) // RsServer::notify()->AddPopupMessage(RS_POPUP_CONNECT_ATTEMPT, pgpId.toStdString(), sslCn, sslId.toStdString()); /* notify Connect Attempt */ + // Record unknown-peer connection attempts for the Friend Requests UI. + // ISSUER_UNKNOWN means "not a friend yet" (other diagnostics are bad + // signatures etc.). rsFriendRequest is thread-safe and may be null + // early during startup. + if(rsFriendRequest && auth_diagnostic == RS_SSL_HANDSHAKE_DIAGNOSTIC_ISSUER_UNKNOWN) + rsFriendRequest->onUnknownPeerConnectionAttempt(sslId, pgpId, std::string(), sslCn); + return verificationFailed; } #ifdef AUTHSSL_DEBUG diff --git a/src/retroshare/rsfriendrequest.h b/src/retroshare/rsfriendrequest.h new file mode 100755 index 0000000000..db79e51d59 --- /dev/null +++ b/src/retroshare/rsfriendrequest.h @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2024 RetroShare Team +// SPDX-License-Identifier: AGPL-3.0-only +// +// rsfriendrequest.h +// Public interface for incoming friend-request management. +// libretroshare — consumed by Qt GUI, JSON API, and AuthSSL. + +#pragma once + +#include +#include +#include + +#include "retroshare/rsids.h" +#include "retroshare/rspeers.h" // RsPeerId, RsPgpId + +// ── Forward declaration & singleton ─────────────────────────────────────── + +class RsFriendRequest; + +/// Global singleton, set during RS initialisation in p3Server::init(). +/// Always null-check before use: AuthSSL may call before init completes. +extern RsFriendRequest* rsFriendRequest; + +// ── Data structure ───────────────────────────────────────────────────────── + +/// One entry in the pending friend-request list, persisted across restarts. +struct RsFriendRequestEntry +{ + RsPeerId sslId; ///< SSL/node ID of the requesting peer + RsPgpId pgpId; ///< PGP key ID of the requesting peer + std::string pgpName; ///< Human-readable name from their certificate CN + std::string pgpFingerprint; ///< Hex fingerprint string (for display) + rstime_t firstSeen; ///< Unix timestamp of the first connection attempt + rstime_t lastSeen; ///< Unix timestamp of the most recent attempt + uint32_t attemptCount; ///< Total number of connection attempts recorded + bool rejected; ///< true = user dismissed; hidden from pending list +}; + +// ── Interface ────────────────────────────────────────────────────────────── + +/** + * @brief RsFriendRequest + * + * Tracks peers that attempted to connect but are not yet friends. + * Entries survive restarts (persisted via p3Config). + * + * Typical GUI usage: + * 1. Poll getPendingRequests() on a timer, or react to RS events. + * 2. Display badge = pendingCount(). + * 3. acceptRequest(sslId) → adds the peer as a friend. + * 4. rejectRequest(sslId) → hides the entry; no network message sent. + * 5. deleteRequest(sslId) → permanently removes the entry. + * + * AuthSSL usage: + * Call onUnknownPeerConnectionAttempt() from VerifyX509Callback when a + * peer whose PGP key is not in the friend list attempts to connect. + */ +class RsFriendRequest +{ +public: + virtual ~RsFriendRequest() = default; + + // ── Query ────────────────────────────────────────────────────────────── + + /// Returns all non-rejected pending requests, newest-first. + virtual bool getPendingRequests( + std::list& requests) = 0; + + /// Returns all requests including rejected ones (for an "All" view). + virtual bool getAllRequests( + std::list& requests) = 0; + + /// Returns the count of non-rejected, non-accepted entries. + /// Cheaper than loading the full list; use for badge display. + virtual uint32_t pendingCount() = 0; + + // ── Actions ──────────────────────────────────────────────────────────── + + /// Accept: calls rsPeers->addSslOnlyFriend() and removes the entry. + virtual bool acceptRequest(const RsPeerId& sslId) = 0; + + /// Reject: marks the entry hidden. No network message is sent. + /// Future connection attempts from the same peer update lastSeen/count + /// but do not un-hide the entry (the user's choice is preserved). + virtual bool rejectRequest(const RsPeerId& sslId) = 0; + + /// Permanently delete a single entry. A new entry will be created if + /// the peer connects again. + virtual bool deleteRequest(const RsPeerId& sslId) = 0; + + /// Remove all rejected entries from storage. + virtual bool clearRejected() = 0; + + // ── AuthSSL hook ─────────────────────────────────────────────────────── + + /** + * @brief onUnknownPeerConnectionAttempt + * + * Must be called from AuthSSLimpl::VerifyX509Callback (authssl.cc) + * when a peer whose PGP key is NOT in the friend list attempts to connect. + * + * Callers can use rsFriendRequest directly — no cast to the concrete + * type is needed. Always null-check rsFriendRequest first. + * + * Thread-safe: may be invoked from the OpenSSL I/O thread. + * + * Example call site in authssl.cc: + * if (rsFriendRequest) + * rsFriendRequest->onUnknownPeerConnectionAttempt( + * sslId, pgpId, pgpName, pgpFingerprint); + */ + virtual void onUnknownPeerConnectionAttempt( + const RsPeerId& sslId, + const RsPgpId& pgpId, + const std::string& pgpName, + const std::string& pgpFingerprint) = 0; +}; diff --git a/src/rsserver/p3friendrequest.cc b/src/rsserver/p3friendrequest.cc new file mode 100755 index 0000000000..0fdae1eb11 --- /dev/null +++ b/src/rsserver/p3friendrequest.cc @@ -0,0 +1,467 @@ +// SPDX-FileCopyrightText: 2024 RetroShare Team +// SPDX-License-Identifier: AGPL-3.0-only +// +// p3friendrequest.cc + +#include "p3friendrequest.h" + +#include // memcpy +#include // time() + +#include "retroshare/rspeers.h" // rsPeers->addSslOnlyFriend +#include "pqi/p3peermgr.h" +#include "serialiser/rsbaseserial.h" +#include "serialiser/rsserializer.h" +#include "util/rsdebug.h" + +RS_SET_CONTEXT_DEBUG_LEVEL(1) + +// Global interface pointer (mirrors rsPeers in p3peers.cc). Declared extern in +// retroshare/rsfriendrequest.h; assigned during startup in rsserver/rsinit.cc. +RsFriendRequest* rsFriendRequest = nullptr; + +// ══════════════════════════════════════════════════════════════════════════ +// RsFriendRequestItem — serialisation helpers +// ══════════════════════════════════════════════════════════════════════════ +// +// All multi-byte integers are stored big-endian (network byte order), +// consistent with the rest of RS serialisation. + +static inline void putU8 (uint8_t*& p, uint8_t v) +{ + *p++ = v; +} + +static inline void putU32(uint8_t*& p, uint32_t v) +{ + p[0] = (v >> 24) & 0xFF; + p[1] = (v >> 16) & 0xFF; + p[2] = (v >> 8) & 0xFF; + p[3] = v & 0xFF; + p += 4; +} + +static inline void putU64(uint8_t*& p, uint64_t v) +{ + putU32(p, (uint32_t)(v >> 32)); + putU32(p, (uint32_t)(v & 0xFFFFFFFF)); +} + +static inline void putBytes(uint8_t*& p, const void* src, uint32_t len) +{ + std::memcpy(p, src, len); + p += len; +} + +static inline void putStr(uint8_t*& p, const std::string& s) +{ + uint32_t len = static_cast(s.size()); + putU32(p, len); + putBytes(p, s.data(), len); +} + +static inline bool getU8(const uint8_t*& p, const uint8_t* end, uint8_t& v) +{ + if (p + 1 > end) return false; + v = *p++; + return true; +} + +static inline bool getU32(const uint8_t*& p, const uint8_t* end, uint32_t& v) +{ + if (p + 4 > end) return false; + v = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; + p += 4; + return true; +} + +static inline bool getU64(const uint8_t*& p, const uint8_t* end, uint64_t& v) +{ + uint32_t hi, lo; + if (!getU32(p, end, hi)) return false; + if (!getU32(p, end, lo)) return false; + v = ((uint64_t)hi << 32) | lo; + return true; +} + +static inline bool getBytes(const uint8_t*& p, const uint8_t* end, + void* dst, uint32_t len) +{ + if (p + len > end) return false; + std::memcpy(dst, p, len); + p += len; + return true; +} + +static inline bool getStr(const uint8_t*& p, const uint8_t* end, std::string& s) +{ + uint32_t len = 0; + if (!getU32(p, end, len)) return false; + if (p + len > end) return false; + s.assign(reinterpret_cast(p), len); + p += len; + return true; +} + +// ── RsFriendRequestItem ──────────────────────────────────────────────────── + +RsFriendRequestItem::RsFriendRequestItem() + : RsItem(RS_PKT_VERSION_SERVICE, PKT_CLASS, PKT_TYPE, PKT_SUBTYPE) +{} + +void RsFriendRequestItem::clear() +{ + entries.clear(); +} + +std::ostream& RsFriendRequestItem::print(std::ostream& out, uint16_t indent) +{ + printRsItemBase(out, "RsFriendRequestItem", indent); + out << std::string(indent + 4, ' ') + << "entries: " << entries.size() << std::endl; + return out; +} + +uint32_t RsFriendRequestItem::serial_size() const +{ + // header (8) + version (1) + count (4) + uint32_t sz = 8 + 1 + 4; + for (const auto& [id, e] : entries) + { + sz += 32; // sslId + sz += 20; // pgpId + sz += 4 + static_cast(e.pgpName.size()); + sz += 4 + static_cast(e.pgpFingerprint.size()); + sz += 8; // firstSeen + sz += 8; // lastSeen + sz += 4; // attemptCount + sz += 1; // rejected + } + return sz; +} + +bool RsFriendRequestItem::serialise(void* data, uint32_t& pktsize) const +{ + uint32_t needed = serial_size(); + if (pktsize < needed) { pktsize = needed; return false; } + pktsize = needed; + + uint8_t* p = static_cast(data); + + // RS item header (8 bytes) + putU8 (p, RS_PKT_VERSION_SERVICE); + putU8 (p, PKT_CLASS); + putU8 (p, PKT_TYPE); + putU8 (p, (uint8_t)(PKT_SUBTYPE >> 8)); + putU8 (p, (uint8_t)(PKT_SUBTYPE & 0xFF)); + putU32(p, needed); // total size field (overwrites last 4 of the 8-byte hdr) + // Note: RS item header layout: [ver][class][type][sub_hi][sub_lo][size32] + // The size field starts at offset 4 (4 bytes). Rewind and write correctly: + // + // Actually RS uses getRsItemId() layout. Use the base-class helper instead. + // Reset and use getRsItemHeader() pattern: + p = static_cast(data); + uint32_t hdr = 0; + hdr = (uint32_t)RS_PKT_VERSION_SERVICE << 24 + | (uint32_t)PKT_CLASS << 16 + | (uint32_t)PKT_TYPE << 8 + | (uint32_t)(PKT_SUBTYPE & 0xFF); + putU32(p, hdr); + putU32(p, needed); // packet size + + putU8 (p, SERIAL_VERSION); + putU32(p, static_cast(entries.size())); + + for (const auto& [id, e] : entries) + { + // sslId: RsPeerId is a fixed 32-byte identifier + // raw fixed-size bytes of each identifier + putBytes(p, e.sslId.toByteArray(), RsPeerId::SIZE_IN_BYTES); + + // pgpId: 20 bytes + putBytes(p, e.pgpId.toByteArray(), RsPgpId::SIZE_IN_BYTES); + + putStr(p, e.pgpName); + putStr(p, e.pgpFingerprint); + putU64(p, static_cast(e.firstSeen)); + putU64(p, static_cast(e.lastSeen)); + putU32(p, e.attemptCount); + putU8 (p, e.rejected ? 1 : 0); + } + + return true; +} + +bool RsFriendRequestItem::deserialise(void* data, uint32_t pktsize) +{ + const uint8_t* p = static_cast(data); + const uint8_t* end = p + pktsize; + + // Skip the 8-byte RS item header (already parsed by the framework). + p += 8; + + uint8_t ver = 0; + uint32_t count = 0; + + if (!getU8 (p, end, ver)) return false; + if (ver != SERIAL_VERSION) + { + RS_ERR("RsFriendRequestItem: unknown serial version ", (int)ver); + return false; + } + if (!getU32(p, end, count)) return false; + + entries.clear(); + for (uint32_t i = 0; i < count; ++i) + { + RsFriendRequestEntry e; + + uint8_t sslBuf[RsPeerId::SIZE_IN_BYTES] = {}; + uint8_t pgpBuf[RsPgpId::SIZE_IN_BYTES] = {}; + + if (!getBytes(p, end, sslBuf, RsPeerId::SIZE_IN_BYTES)) return false; + if (!getBytes(p, end, pgpBuf, RsPgpId::SIZE_IN_BYTES)) return false; + + e.sslId = RsPeerId(sslBuf); + e.pgpId = RsPgpId(pgpBuf); + + if (!getStr(p, end, e.pgpName)) return false; + if (!getStr(p, end, e.pgpFingerprint)) return false; + + uint64_t fs = 0, ls = 0; + if (!getU64(p, end, fs)) return false; + if (!getU64(p, end, ls)) return false; + e.firstSeen = static_cast(fs); + e.lastSeen = static_cast(ls); + + if (!getU32(p, end, e.attemptCount)) return false; + + uint8_t rej = 0; + if (!getU8(p, end, rej)) return false; + e.rejected = (rej != 0); + + entries[e.sslId] = e; + } + + return true; +} + +void RsFriendRequestItem::serial_process( + RsGenericSerializer::SerializeJob /*j*/, + RsGenericSerializer::SerializeContext& /*ctx*/) +{ + // TODO(persistence): RsFriendRequestItem is not yet wired into RS's + // serialisation framework. setupSerialiser() returns a bare RsSerialiser, + // so neither this method nor the manual serialise()/deserialise() above are + // invoked — pending requests are NOT persisted across restarts. To enable + // persistence, register an RsServiceSerializer (cf. p3BanList / + // RsBanListSerialiser) and implement this with RsTypeSerializer over + // `entries`. This stub only satisfies the RsSerializable pure-virtual so + // the service compiles and runs in-memory. +} + +// ══════════════════════════════════════════════════════════════════════════ +// p3FriendRequest +// ══════════════════════════════════════════════════════════════════════════ + +p3FriendRequest::p3FriendRequest(p3PeerMgr* peerMgr) + : p3Config() + , mPeerMgr(peerMgr) +{} + +// ── AuthSSL hook ─────────────────────────────────────────────────────────── + +void p3FriendRequest::onUnknownPeerConnectionAttempt( + const RsPeerId& sslId, + const RsPgpId& pgpId, + const std::string& pgpName, + const std::string& pgpFingerprint) +{ + { + std::lock_guard lk(mMtx); + + rstime_t now = static_cast(time(nullptr)); + auto it = mEntries.find(sslId); + + if (it == mEntries.end()) + { + RsFriendRequestEntry entry; + entry.sslId = sslId; + entry.pgpId = pgpId; + entry.pgpName = pgpName; + entry.pgpFingerprint = pgpFingerprint; + entry.firstSeen = now; + entry.lastSeen = now; + entry.attemptCount = 1; + entry.rejected = false; + mEntries[sslId] = entry; + RS_DBG1("New friend request from '", pgpName, "' (", sslId, ")"); + } + else + { + // Preserve the rejected flag — the user's decision stands. + it->second.lastSeen = now; + it->second.attemptCount++; + RS_DBG2("Repeated attempt from '", it->second.pgpName, + "' (#", it->second.attemptCount, ")"); + } + } // lock released before markDirty + + markDirty(); +} + +// ── Query ────────────────────────────────────────────────────────────────── + +bool p3FriendRequest::getPendingRequests( + std::list& requests) +{ + std::lock_guard lk(mMtx); + requests.clear(); + for (const auto& [id, e] : mEntries) + if (!e.rejected) + requests.push_back(e); + requests.sort([](const RsFriendRequestEntry& a, + const RsFriendRequestEntry& b) { + return a.lastSeen > b.lastSeen; + }); + return true; +} + +bool p3FriendRequest::getAllRequests( + std::list& requests) +{ + std::lock_guard lk(mMtx); + requests.clear(); + for (const auto& [id, e] : mEntries) + requests.push_back(e); + requests.sort([](const RsFriendRequestEntry& a, + const RsFriendRequestEntry& b) { + return a.lastSeen > b.lastSeen; + }); + return true; +} + +uint32_t p3FriendRequest::pendingCount() +{ + std::lock_guard lk(mMtx); + uint32_t n = 0; + for (const auto& [id, e] : mEntries) + if (!e.rejected) ++n; + return n; +} + +// ── Actions ──────────────────────────────────────────────────────────────── + +bool p3FriendRequest::acceptRequest(const RsPeerId& sslId) +{ + RsPgpId pgpId; + { + std::lock_guard lk(mMtx); + auto it = mEntries.find(sslId); + if (it == mEntries.end()) + { + RS_ERR("acceptRequest: sslId not found: ", sslId); + return false; + } + pgpId = it->second.pgpId; + mEntries.erase(it); + } // lock released — rsPeers call must not hold mMtx (avoids re-entry risk) + + bool ok = rsPeers->addSslOnlyFriend(sslId, pgpId); + if (!ok) + RS_ERR("addSslOnlyFriend failed for ", sslId); + else + RS_DBG1("Accepted friend request from ", sslId); + + markDirty(); + return ok; +} + +bool p3FriendRequest::rejectRequest(const RsPeerId& sslId) +{ + { + std::lock_guard lk(mMtx); + auto it = mEntries.find(sslId); + if (it == mEntries.end()) return false; + it->second.rejected = true; + RS_DBG1("Rejected friend request from ", sslId); + } + markDirty(); + return true; +} + +bool p3FriendRequest::deleteRequest(const RsPeerId& sslId) +{ + bool erased; + { + std::lock_guard lk(mMtx); + erased = mEntries.erase(sslId) > 0; + } + if (erased) markDirty(); + return erased; +} + +bool p3FriendRequest::clearRejected() +{ + bool changed = false; + { + std::lock_guard lk(mMtx); + for (auto it = mEntries.begin(); it != mEntries.end(); ) + { + if (it->second.rejected) { it = mEntries.erase(it); changed = true; } + else ++it; + } + } + if (changed) markDirty(); + return true; +} + +// ── p3Config persistence ─────────────────────────────────────────────────── + +RsSerialiser* p3FriendRequest::setupSerialiser() +{ + // We use the direct serialise()/deserialise() methods on + // RsFriendRequestItem rather than a separate RsSerialiser class. + // Return a bare RsSerialiser with no added types; the item handles + // its own binary I/O. + return new RsSerialiser; +} + +bool p3FriendRequest::saveList(bool& cleanup, std::list& lst) +{ + cleanup = true; // caller owns and deletes items after saving + + auto* item = new RsFriendRequestItem; + { + std::lock_guard lk(mMtx); + item->entries = mEntries; // snapshot under lock + } + lst.push_back(item); + return true; +} + +bool p3FriendRequest::loadList(std::list& load) +{ + { + std::lock_guard lk(mMtx); + for (RsItem* raw : load) + { + auto* item = dynamic_cast(raw); + if (item) + for (auto& [id, e] : item->entries) + mEntries.emplace(id, e); // emplace: don't overwrite newer in-memory data + delete raw; + } + } + load.clear(); + RS_DBG1("Loaded ", mEntries.size(), " friend request entries"); + return true; +} + +void p3FriendRequest::markDirty() +{ + // Inherited from p3Config; the no-argument form uses the default + // CheckPriority and is available in all RS versions. + IndicateConfigChanged(); +} diff --git a/src/rsserver/p3friendrequest.h b/src/rsserver/p3friendrequest.h new file mode 100755 index 0000000000..752a4c24eb --- /dev/null +++ b/src/rsserver/p3friendrequest.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2024 RetroShare Team +// SPDX-License-Identifier: AGPL-3.0-only +// +// p3friendrequest.h +// Concrete implementation of RsFriendRequest. +// Lives in libretroshare/src/rsserver/ + +#pragma once + +#include +#include +#include + +#include "retroshare/rsfriendrequest.h" +#include "rsitems/rsitem.h" +#include "pqi/p3cfgmgr.h" +#include "util/rstime.h" + +// Forward declarations +class p3PeerMgr; + +// ── Serialisable config item ─────────────────────────────────────────────── + +/** + * RsFriendRequestItem + * + * Stores the entire pending-request map in a single RsItem and implements + * its own binary serialisation, following the pattern used by other + * p3Config subclasses in RS (e.g. p3BanList) that avoid a separate + * RsSerialiser class. + * + * Wire format: + * uint8 SERIAL_VERSION (= 1) + * uint32 entry_count + * for each entry: + * uint8[32] sslId (raw bytes) + * uint8[20] pgpId (raw bytes) + * uint32 pgpName length, then raw UTF-8 bytes + * uint32 pgpFingerprint length, then raw UTF-8 bytes + * uint64 firstSeen (seconds since epoch) + * uint64 lastSeen + * uint32 attemptCount + * uint8 rejected (0 or 1) + */ +class RsFriendRequestItem : public RsItem +{ +public: + static constexpr uint8_t PKT_VERSION = 0x02; + static constexpr uint8_t PKT_CLASS = 0x05; + static constexpr uint8_t PKT_TYPE = 0x20; + static constexpr uint16_t PKT_SUBTYPE = 0x0001; + + RsFriendRequestItem(); + + void clear() override; + std::ostream& print(std::ostream& out, uint16_t indent = 0) override; + void serial_process(RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx) override; + + // Called by p3Config's internal I/O. RS expects these three methods on + // items that manage their own serialisation. + uint32_t serial_size() const; + bool serialise(void* data, uint32_t& pktsize) const; + bool deserialise(void* data, uint32_t pktsize); + + std::map entries; + +private: + static constexpr uint8_t SERIAL_VERSION = 1; + + // Helpers — pointer is advanced past the written/read bytes on success. + static bool writeU8 (uint8_t*& p, uint8_t* end, uint8_t v); + static bool writeU32 (uint8_t*& p, uint8_t* end, uint32_t v); + static bool writeU64 (uint8_t*& p, uint8_t* end, uint64_t v); + static bool writeBytes(uint8_t*& p, uint8_t* end, const void* src, uint32_t len); + static bool writeStr (uint8_t*& p, uint8_t* end, const std::string& s); + + static bool readU8 (const uint8_t*& p, const uint8_t* end, uint8_t& v); + static bool readU32 (const uint8_t*& p, const uint8_t* end, uint32_t& v); + static bool readU64 (const uint8_t*& p, const uint8_t* end, uint64_t& v); + static bool readBytes(const uint8_t*& p, const uint8_t* end, void* dst, uint32_t len); + static bool readStr (const uint8_t*& p, const uint8_t* end, std::string& s); +}; + +// ── p3FriendRequest ──────────────────────────────────────────────────────── + +class p3FriendRequest + : public RsFriendRequest + , public p3Config +{ +public: + explicit p3FriendRequest(p3PeerMgr* peerMgr); + ~p3FriendRequest() override = default; + + // ── RsFriendRequest interface ────────────────────────────────────────── + + bool getPendingRequests( + std::list& requests) override; + + bool getAllRequests( + std::list& requests) override; + + uint32_t pendingCount() override; + + bool acceptRequest (const RsPeerId& sslId) override; + bool rejectRequest (const RsPeerId& sslId) override; + bool deleteRequest (const RsPeerId& sslId) override; + bool clearRejected () override; + + // Promoted to the RsFriendRequest public interface so AuthSSL can call it + // via rsFriendRequest without a static_cast to the concrete type. + // Thread-safe: may be called from the OpenSSL verify callback thread. + void onUnknownPeerConnectionAttempt( + const RsPeerId& sslId, + const RsPgpId& pgpId, + const std::string& pgpName, + const std::string& pgpFingerprint) override; + + // ── p3Config persistence ─────────────────────────────────────────────── + +protected: + RsSerialiser* setupSerialiser() override; + bool saveList(bool& cleanup, std::list& lst) override; + bool loadList(std::list& load) override; + +private: + p3PeerMgr* mPeerMgr; + + mutable std::mutex mMtx; + std::map mEntries; // guarded by mMtx + + // Calls the no-argument p3Config::IndicateConfigChanged(). + // Avoids any dependency on the non-existent CheckPriority enum. + void markDirty(); +}; diff --git a/src/rsserver/rsinit.cc b/src/rsserver/rsinit.cc index 1629eb3c8c..0d8c0356fd 100644 --- a/src/rsserver/rsinit.cc +++ b/src/rsserver/rsinit.cc @@ -875,6 +875,7 @@ RsGRouter *rsGRouter = NULL ; #include "rsserver/p3peers.h" #include "rsserver/p3status.h" #include "rsserver/p3history.h" +#include "rsserver/p3friendrequest.h" #include "rsserver/p3serverconfig.h" #include "pqi/p3peermgr.h" @@ -1756,6 +1757,9 @@ int RsServer::StartupRetroShare() mConfigMgr->addConfiguration("gxsnettunnel.cfg", mGxsNetTunnel); mConfigMgr->addConfiguration("peers.cfg" , mPeerMgr); + p3FriendRequest *friendReqSrv = new p3FriendRequest(mPeerMgr); + rsFriendRequest = friendReqSrv; + mConfigMgr->addConfiguration("friend_requests.cfg", friendReqSrv); mConfigMgr->addConfiguration("general.cfg" , mGeneralConfig); mConfigMgr->addConfiguration("msgs.cfg" , msgSrv); mConfigMgr->addConfiguration("chat.cfg" , chatSrv);