|
|
|
@ -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,24 +92,46 @@ struct DCCPriv |
|
|
|
|
if (!ec) return; // No errors.
|
|
|
|
|
std::cerr << fmt::format("'- DCC write error: {} ({})", SystemErrorToIRCError(ec), ec.message()) << std::endl; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void startListener() |
|
|
|
|
{ |
|
|
|
|
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) |
|
|
|
|
disconnected(ec); |
|
|
|
|
else |
|
|
|
|
connected(ec); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx) |
|
|
|
|
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", "0")) |
|
|
|
|
{ |
|
|
|
|
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){ |
|
|
|
|
if (ec == asio::error::operation_aborted) |
|
|
|
|
return; |
|
|
|
|
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"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (ec) |
|
|
|
|
mp->disconnected(ec); |
|
|
|
|
else |
|
|
|
|
mp->connected(ec); |
|
|
|
|
}); |
|
|
|
|
mp->ip = ipAddress; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& ip, const std::string& port) |
|
|
|
@ -127,23 +152,50 @@ 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...
|
|
|
|
|
|
|
|
|
|
mp->resolverResults = mp->resolver.resolve(mp->ip, mp->port); |
|
|
|
|
|
|
|
|
|
asio::async_connect(mp->socket, mp->resolverResults, |
|
|
|
|
[this](const asio::error_code& ec, const tcp::endpoint&) { |
|
|
|
|
if (ec) |
|
|
|
|
mp->disconnected(ec); |
|
|
|
|
auto acceptNormally = [this] { |
|
|
|
|
mp->resolverResults = mp->resolver.resolve(mp->ip, mp->port); |
|
|
|
|
|
|
|
|
|
asio::async_connect(mp->socket, mp->resolverResults, |
|
|
|
|
[this](const asio::error_code& ec, const tcp::endpoint&) { |
|
|
|
|
if (ec) |
|
|
|
|
mp->disconnected(ec); |
|
|
|
|
else |
|
|
|
|
mp->connected(ec); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
mp->connected(ec); |
|
|
|
|
}); |
|
|
|
|
NATPortMapping::remove(ipAddress, port(), port()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
mp->ircctx.ctcpRequest(targetNickname, "DCC", fmt::format("CHAT chat {} {}", |
|
|
|
|
normalIpToLong(ipAddress), |
|
|
|
|
port())); |
|
|
|
|
|
|
|
|
|
mp->pending = false; |
|
|
|
|
return IRCError::NoError; |
|
|
|
|
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); |
|
|
|
|