#2 DCC with reverse initiation functionality and NAT integration.

master
Tomatix 3 years ago
parent 29ea535243
commit 433fdf747c
  1. 13
      ICommand/Internal/dcc.cpp
  2. 9
      IRCClient/CMakeLists.txt
  3. 79
      IRCClient/DCC.cpp
  4. 15
      IRCClient/DCC.h
  5. 4
      IRCClient/IRCBasePriv.h
  6. 46
      IRCClient/Utilities.cpp
  7. 4
      IRCClient/Utilities.h
  8. 32
      IWin/IWinDCCChat.cpp
  9. 11
      IWin/IWinDCCChat.h
  10. 31
      IdealIRC/IdealIRC.cpp
  11. 2
      IdealIRC/IdealIRC.h
  12. 15
      IdealIRC/MdiManager.cpp
  13. 2
      IdealIRC/MdiManager.h

@ -22,9 +22,17 @@ void ICommandPriv::cmd_dcc(const std::string& command, const std::string& target
.arg( QString::fromStdString(target), errorStr, QString::number(code)) );
}
else {
const auto portno = result.first->port();
auto dcc = result.first;
const auto portno = dcc->isReversed() ? 0
: dcc->port();
auto* window = mdi.createDCCSubwindow(mdi.currentStatus(), IWin::Type::DCCChat, result.first, IRCPrefix(target));
window->print(PrintType::ProgramInfo, QObject::tr("Initiated CHAT with %1 on port %2")
if (dcc->isReversed())
window->print(PrintType::ProgramInfo, QObject::tr("Initiating CHAT with %1, please wait...")
.arg( QString::fromStdString(target) ));
else
window->print(PrintType::ProgramInfo, QObject::tr("Initiating CHAT with %1 on port %2, please wait...")
.arg( QString::fromStdString(target) )
.arg(portno));
@ -33,6 +41,7 @@ void ICommandPriv::cmd_dcc(const std::string& command, const std::string& target
normalIpToLong(myIp),
portno
);
cmd_ctcp(target, "DCC", msg);
}
}

@ -70,5 +70,12 @@ list(APPEND ${component}_PRIVATE
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_COMMANDS} ${${component}_PRIVATE})
target_link_libraries(${component} fmt crypto ssl)
target_link_libraries(${component}
NATUtils
fmt
crypto
ssl
)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -7,6 +7,9 @@
#include "DCC.h"
#include "IRCBase.h"
#include "NATUtils/PortMapping.h"
#include "NATUtils/PublicAddress.h"
#include "Utilities.h"
#include <fmt/format.h>
#include <iostream>
@ -89,25 +92,47 @@ struct DCCPriv
if (!ec) return; // No errors.
std::cerr << fmt::format("'- DCC write error: {} ({})", SystemErrorToIRCError(ec), ec.message()) << std::endl;
}
};
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx)
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", "0"))
void startListener()
{
mp->acceptor.open(mp->endpoint.protocol());
mp->acceptor.set_option(tcp::acceptor::reuse_address(true));
mp->acceptor.bind(mp->endpoint);
mp->acceptor.listen(1);
mp->acceptor.async_accept(mp->socket, [this](const asio::error_code& ec){
acceptor.open(endpoint.protocol());
acceptor.set_option(tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen(1);
acceptor.async_accept(socket, [this](const asio::error_code& ec){
if (ec == asio::error::operation_aborted)
return;
if (ec)
mp->disconnected(ec);
disconnected(ec);
else
mp->connected(ec);
connected(ec);
});
}
};
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx)
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", "0"))
{
auto ipAddress = findOwnIpAddress();
mp->startListener(); // also acquires a port for us
if (isPrivateIp(ipAddress) && NATPortMapping::add(ipAddress, port(), port())) {
auto publicIp = NATPublicAddress::get();
if (publicIp.has_value()) {
ipAddress = longIpToNormal(*publicIp);
}
else {
NATPortMapping::remove(ipAddress, port(), port());
// Reset internal state, effectively stopping the listening.
// This DCC instance will be destroyed and replaced when we get a response with the port to use in reverse.
mp = std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", "0");
}
}
mp->ip = ipAddress;
}
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& ip, const std::string& port)
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Target, ioctx, ip, port))
@ -127,11 +152,12 @@ bool DCC::isPending() const
return mp->pending;
}
IRCError DCC::accept()
IRCError DCC::accept(const std::string& targetNickname)
{
if (mp->direction == Direction::Initiator)
return IRCError::DCC_NotATarget; // Only usable if we are a Target...
auto acceptNormally = [this] {
mp->resolverResults = mp->resolver.resolve(mp->ip, mp->port);
asio::async_connect(mp->socket, mp->resolverResults,
@ -144,6 +170,32 @@ IRCError DCC::accept()
mp->pending = false;
return IRCError::NoError;
};
auto acceptReversed = [this, &targetNickname] {
auto ipAddress = findOwnIpAddress();
mp->startListener();
if (isPrivateIp(ipAddress) && NATPortMapping::add(ipAddress, port(), port())) {
auto publicIp = NATPublicAddress::get();
if (publicIp.has_value())
ipAddress = longIpToNormal(*publicIp);
else
NATPortMapping::remove(ipAddress, port(), port());
}
mp->ircctx.ctcpRequest(targetNickname, "DCC", fmt::format("CHAT chat {} {}",
normalIpToLong(ipAddress),
port()));
mp->pending = false;
return IRCError::NoError;
};
if (mp->port == "0")
return acceptReversed();
else
return acceptNormally();
}
void DCC::disconnect()
@ -203,6 +255,11 @@ const std::string& DCC::ip() const
return mp->ip;
}
bool DCC::isReversed() const
{
return mp->port == "0";
}
void DCC::setCallbackRead(DCC::CallbackRead&& cb)
{
mp->cbRead = std::move(cb);

@ -47,7 +47,7 @@ public:
DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& ip, const std::string& port);
[[nodiscard]] bool isPending() const;
IRCError accept();
IRCError accept(const std::string& targetNickname);
void disconnect();
@ -59,9 +59,22 @@ public:
const IRCBase& context() const;
bool isConnected() const;
/*
* As initiator: Port to listen on.
* Reversed initiator: Always zero.
* As target: Port to connect to.
*/
uint16_t port() const;
/*
* As initiator: IP to listen on (published in CTCP DCC request)
* As target or reversed initiator: IP to connect to.
*/
const std::string& ip() const;
bool isReversed() const;
/*
* Callbacks used by 'users' of this class.
*/

@ -24,8 +24,8 @@
namespace {
/* Debug/development options */
constexpr bool DumpReadData = true;
constexpr bool DumpWriteData = true;
constexpr bool DumpReadData = false;
constexpr bool DumpWriteData = false;
const std::vector<std::string> V3Support {
"account-notify",

@ -15,23 +15,6 @@
namespace {
constexpr auto DefaultIpAddress{ "127.0.0.1" };
uint32_t ipStrToInt(const std::string& ip)
{
std::stringstream ss(ip);
std::string p;
std::vector<unsigned> parts;
while (getline(ss, p, '.'))
parts.emplace_back(std::stoul(p));
uint32_t ret = parts[0];
for (int i = 1; i < 4; ++i) {
ret <<= 8u;
ret |= parts[i];
}
return ret;
}
std::string getIpFromSecondToken(const std::string& input)
{
if (input.empty())
@ -127,24 +110,41 @@ bool singleWildcardMatch(const std::string& str, const std::string& wc)
return !(hasLeft && hasRight && leftEnd == rightBegin);
}
std::string longIpToNormal(const std::string& longip)
std::string longIpToNormal(std::uint32_t longip)
{
try {
unsigned ip = std::stoul(longip);
unsigned ip = longip;
unsigned a = (ip & 0xFF000000u) >> 24u;
unsigned b = (ip & 0x00FF0000u) >> 16u;
unsigned c = (ip & 0x0000FF00u) >> 8u;
unsigned d = ip & 0x000000FFu;
return fmt::format("{}.{}.{}.{}", a, b, c, d);
}
std::string longIpToNormal(const std::string& longip)
{
try {
return longIpToNormal( std::stoul(longip) );
}
catch (...) {
return "0.0.0.0";
}
}
std::string normalIpToLong(const std::string& normalip)
std::uint32_t normalIpToLong(const std::string& normalip)
{
return std::to_string(ipStrToInt(normalip));
std::stringstream ss(normalip);
std::string p;
std::vector<unsigned> parts;
while (getline(ss, p, '.'))
parts.emplace_back(std::stoul(p));
uint32_t ret = parts[0];
for (int i = 1; i < 4; ++i) {
ret <<= 8u;
ret |= parts[i];
}
return ret;
}
std::string concatenateModes(const std::unordered_map<char, std::string>& modes)
@ -292,7 +292,7 @@ bool isPrivateIp(const std::string& ip)
* Class C: 192.168.0.0 - 192.168.255.255
*/
const auto intip{ ipStrToInt(ip) };
const auto intip{ normalIpToLong(ip) };
const auto classIp{ (intip & 0xFF000000) >> 24 };

@ -8,6 +8,7 @@
#ifndef IRCUTILITIES_H
#define IRCUTILITIES_H
#include <cstdint>
#include <unordered_map>
#include <string>
#include <algorithm>
@ -16,8 +17,9 @@ constexpr char CTCPflag { 0x01 };
std::pair<std::string,std::string> FormatCTCPLine(std::string line);
bool singleWildcardMatch(const std::string& str, const std::string& wc);
std::string longIpToNormal(std::uint32_t longip);
std::string longIpToNormal(const std::string& longip);
std::string normalIpToLong(const std::string& normalip);
std::uint32_t normalIpToLong(const std::string& normalip);
std::string concatenateModes(const std::unordered_map<char, std::string>& modes);
bool strEquals(const std::string& l, const std::string& r);
std::string toBase64(const std::string& input);

@ -6,12 +6,13 @@
*/
#include "IWinDCCChat.h"
#include "IWinStatus.h"
#include <IRCClient/IRCBase.h>
#include <QMessageBox>
#include <iostream>
IWinDCCChat::IWinDCCChat(std::shared_ptr<DCC> dcc_, const QString& targetNickname)
: IWin(IWin::Type::DCCChat, nullptr)
IWinDCCChat::IWinDCCChat(IWinStatus* statusParent, std::shared_ptr<DCC> dcc_, const QString& targetNickname)
: IWin(IWin::Type::DCCChat, statusParent)
, dcc(std::move(dcc_))
{
setButtonText(targetNickname);
@ -32,9 +33,7 @@ IWinDCCChat::IWinDCCChat(std::shared_ptr<DCC> dcc_, const QString& targetNicknam
connect(input, &ILineEdit::newLine,
this, &IWinDCCChat::newLine);
dcc->setCallbackConnected([this]{ onConnected(); });
dcc->setCallbackDisconnected([this](IRCError e){ onDisconnected(e); });
dcc->setCallbackRead([this](const DCC::ByteString& data, std::size_t length){ onRead(data, length); });
setupCallbacks();
}
bool IWinDCCChat::printWithCustomTime(const QDateTime& timestamp, PrintType ptype, const QString& text)
@ -51,10 +50,23 @@ void IWinDCCChat::refreshWindowTitle()
void IWinDCCChat::acceptAsTarget()
{
const auto ip = QString::fromStdString(dcc->ip());
if (dcc->isReversed()) {
print(PrintType::ProgramInfo, tr("Waiting for participant..."));
}
else {
const auto port = QString::number(dcc->port());
print(PrintType::ProgramInfo, tr("Connecting to %1:%2...").arg(ip, port));
}
dcc->accept(getButtonText().toStdString());
}
dcc->accept();
void IWinDCCChat::setupCallbacks()
{
dcc->setCallbackConnected([this]{ onConnected(); });
dcc->setCallbackDisconnected([this](IRCError e){ onDisconnected(e); });
dcc->setCallbackRead([this](const DCC::ByteString& data, std::size_t length){ onRead(data, length); });
}
void IWinDCCChat::closeEvent(QCloseEvent* evt)
@ -139,3 +151,11 @@ void IWinDCCChat::disconnectFromDCC()
{
dcc->disconnect();
}
void IWinDCCChat::initiateReversed(std::shared_ptr<DCC> dcc_)
{
reverseInitiatePending = false;
dcc = std::move(dcc_);
setupCallbacks();
dcc->accept(getButtonText().toStdString());
}

@ -15,22 +15,28 @@
#include <QCloseEvent>
#include <memory>
class IWinStatus;
class IWinDCCChat : public IWin
{
Q_OBJECT
public:
IWinDCCChat(std::shared_ptr<DCC> dcc_, const QString& targetNickname);
IWinDCCChat(IWinStatus* statusParent, std::shared_ptr<DCC> dcc_, const QString& targetNickname);
bool printWithCustomTime(const QDateTime& timestamp, PrintType ptype, const QString& text) override;
void refreshWindowTitle() override;
void clear() override { view->resetView(); }
void acceptAsTarget();
void disconnectFromDCC();
void markReverseInitiatePending() { reverseInitiatePending = true; }
bool isReverseInitiatePending() const { return reverseInitiatePending; }
void initiateReversed(std::shared_ptr<DCC> dcc_);
private:
void setupCallbacks();
void closeEvent(QCloseEvent* evt) override;
void onConnected();
void onDisconnected(IRCError e);
@ -43,6 +49,7 @@ private:
ILineEdit* input;
QString lineBuffer;
bool reverseInitiatePending{ false };
std::shared_ptr<DCC> dcc;
};

@ -8,7 +8,10 @@
#include "IdealIRC.h"
#include "ui_IdealIRC.h"
#include "config.h"
#include "IWin/IWinStatus.h"
#include "IWin/IWinDCCChat.h"
#include <ConfigMgr.h>
#include <QDebug>
#include <QDir>
@ -367,15 +370,37 @@ void IdealIRC::onStatusWindowCreated(IWinStatus* window)
}
});
connect(&(window->getConnection()), &IRC::DCCRequested, this, &IdealIRC::onDCCRequested);
connect(&(window->getConnection()), &IRC::DCCRequested, [this,window](const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message) {
onDCCRequested(*window, dcc, sender, type, message);
});
}
void IdealIRC::onDCCRequested(const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message)
void IdealIRC::onDCCRequested(const IWinStatus& statusParent, const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message)
{
// TODO check type and message together... DCC SEND would use 'message' to hold filename and size
/*
* Need to check any DCC windows already open in the given status parent of the inbound DCC for pending reverses.
* Then compare for any matching pending reverses with this inbound DCC.
*/
auto dccChatList = MdiManager::instance().childrenOf(&statusParent, IWin::Type::DCCChat);
bool requestHandled{ false };
for (auto* subwin : dccChatList) {
auto* dccwin = dynamic_cast<IWinDCCChat*>(subwin);
if (!dccwin) {
qWarning() << "Unable to cast a window marked as type DCCChat!?";
continue;
}
if (QString::fromStdString(sender.toString()) == dccwin->getButtonText() && dccwin->isReverseInitiatePending()) {
requestHandled = true;
dccwin->initiateReversed(dcc);
}
}
if (!requestHandled) {
dccQueryDlg.addQuery(sender, type, dcc);
dccQueryDlg.show();
}
}
void IdealIRC::on_actionConnect_triggered()
{

@ -35,7 +35,7 @@ public:
private slots:
void onStatusWindowCreated(IWinStatus* window);
void onDCCRequested(const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message);
void onDCCRequested(const IWinStatus& statusParent, const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message);
void on_actionConnect_triggered();
void on_actionOptions_triggered();
void on_actionAbout_IdealIRC_triggered();

@ -109,17 +109,26 @@ IWin* MdiManager::createSubwindow(IWin* parent, const QString& buttonText, IWin:
return createSubwindowCommons(parent, basePtr, buttonText, iconPath, activate, buttonHighlight);
}
IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& target)
IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, const std::shared_ptr<DCC>& dcc, const IRCPrefix& target)
{
IWin* basePtr{ nullptr };
IWinStatus* statusParent{ nullptr };
QString iconPath;
auto targetNickname{ QString::fromStdString(target.toString()) };
if (parent && parent->getType() == IWin::Type::Status)
statusParent = dynamic_cast<IWinStatus*>(parent);
switch (windowType) {
case IWin::Type::DCCChat:
basePtr = new IWinDCCChat(std::move(dcc), targetNickname);
case IWin::Type::DCCChat: {
auto* winPtr = new IWinDCCChat(statusParent, dcc, targetNickname);
if (dcc->isReversed())
winPtr->markReverseInitiatePending();
basePtr = winPtr;
iconPath = ":/Icons/private.png";
break;
}
default:
break;

@ -34,7 +34,7 @@ public:
static MdiManager& instance();
IWin* createSubwindow(IWin* parent, const QString& buttonText, IWin::Type windowType, bool activate = true, Highlight buttonHighlight = HL_None);
IWin* createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& target);
IWin* createDCCSubwindow(IWin* parent, IWin::Type windowType, const std::shared_ptr<DCC>& dcc, const IRCPrefix& target);
IWin* currentWindow() const;
IWinStatus* currentStatus() const;

Loading…
Cancel
Save