peerfactory.cpp
presetnetworks.cpp
quassel.cpp
+ proxyline.cpp
remotepeer.cpp
settings.cpp
signalproxy.cpp
QTcpSocket* socket() const;
- bool isLocal() const;
+ virtual bool isLocal() const;
virtual void handle(const Protocol::RegisterClient&) { invalidMessage(); }
virtual void handle(const Protocol::ClientDenied&) { invalidMessage(); }
return result;
}
-/**
- * Extracts a space-delimited fragment from an IRC message
- * @param raw Raw Message
- * @param start Current index into the message, will be advanced automatically
- * @param end End of fragment, if already known. Default is -1, in which case it will be set to the next whitespace
- * character or the end of the string
- * @param prefix Required prefix. Default is 0. If set, this only parses a fragment if it starts with the given prefix.
- * @return Fragment
- */
-QByteArray extractFragment(const QByteArray& raw, int& start, int end = -1, char prefix = 0)
+QByteArray IrcDecoder::extractFragment(const QByteArray& raw, int& start, int end, char prefix)
{
// Try to set find the end of the space-delimited fragment
if (end == -1) {
return fragment;
}
-/**
- * Skips empty parts in the message
- * @param raw Raw Message
- * @param start Current index into the message, will be advanced automatically
- */
-void skipEmptyParts(const QByteArray& raw, int& start)
+void IrcDecoder::skipEmptyParts(const QByteArray& raw, int& start)
{
while (start < raw.length() && raw[start] == ' ') {
start++;
* @param parameters[out] Parsed list of parameters
*/
static void parseMessage(const std::function<QString(const QByteArray&)>& decode, const QByteArray& raw, QHash<IrcTagKey, QString>& tags, QString& prefix, QString& command, QList<QByteArray>& parameters);
+
+ /**
+ * Extracts a space-delimited fragment from an IRC message
+ * @param raw Raw Message
+ * @param start Current index into the message, will be advanced automatically
+ * @param end End of fragment, if already known. Default is -1, in which case it will be set to the next whitespace
+ * character or the end of the string
+ * @param prefix Required prefix. Default is 0. If set, this only parses a fragment if it starts with the given prefix.
+ * @return Fragment
+ */
+ static QByteArray extractFragment(const QByteArray& raw, int& start, int end = -1, char prefix = 0);
+
+ /**
+ * Skips empty parts in the message
+ * @param raw Raw Message
+ * @param start Current index into the message, will be advanced automatically
+ */
+ static void skipEmptyParts(const QByteArray& raw, int& start);
private:
/**
* Parses an encoded IRCv3 message tag value
const quint32 magic = 0x42b33f00;
+const quint32 proxyMagic = 0x50524f58;
+
enum Type
{
InternalProtocol = 0x00,
// enable compression now if requested - the initial handshake is uncompressed in the legacy protocol!
_useCompression = socket()->property("UseCompression").toBool();
if (_useCompression)
- qDebug() << "Using compression for peer:" << qPrintable(socket()->peerAddress().toString());
+ qDebug() << "Using compression for peer:" << qPrintable(address());
}
}
--- /dev/null
+/***************************************************************************
+ * Copyright (C) 2005-2020 by the Quassel Project *
+ * devel@quassel-irc.org *
+ * *
+ * 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 2 of the License, or *
+ * (at your option) version 3. *
+ * *
+ * 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; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include "proxyline.h"
+
+#include "ircdecoder.h"
+
+ProxyLine ProxyLine::parseProxyLine(const QByteArray& line)
+{
+ ProxyLine result;
+
+ int start = 0;
+ if (line.startsWith("PROXY")) {
+ start = 5;
+ }
+ IrcDecoder::skipEmptyParts(line, start);
+ QByteArray protocol = IrcDecoder::extractFragment(line, start);
+ if (protocol == "TCP4") {
+ result.protocol = QAbstractSocket::IPv4Protocol;
+ } else if (protocol == "TCP6") {
+ result.protocol = QAbstractSocket::IPv6Protocol;
+ } else {
+ result.protocol = QAbstractSocket::UnknownNetworkLayerProtocol;
+ }
+
+ if (result.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
+ bool ok;
+ IrcDecoder::skipEmptyParts(line, start);
+ result.sourceHost = QHostAddress(QString::fromLatin1(IrcDecoder::extractFragment(line, start)));
+ IrcDecoder::skipEmptyParts(line, start);
+ result.sourcePort = QString::fromLatin1(IrcDecoder::extractFragment(line, start)).toUShort(&ok);
+ if (!ok) result.sourcePort = 0;
+ IrcDecoder::skipEmptyParts(line, start);
+ result.targetHost = QHostAddress(QString::fromLatin1(IrcDecoder::extractFragment(line, start)));
+ IrcDecoder::skipEmptyParts(line, start);
+ result.targetPort = QString::fromLatin1(IrcDecoder::extractFragment(line, start)).toUShort(&ok);
+ if (!ok) result.targetPort = 0;
+ }
+
+ return result;
+}
+
+
+QDebug operator<<(QDebug dbg, const ProxyLine& p) {
+ dbg.nospace();
+ dbg << "(protocol = " << p.protocol;
+ if (p.protocol == QAbstractSocket::UnknownNetworkLayerProtocol) {
+ dbg << ")";
+ } else {
+ dbg << ", sourceHost = " << p.sourceHost << ", sourcePort = " << p.sourcePort << ", targetHost = " << p.targetHost << ", targetPort = " << p.targetPort << ")";
+ }
+ return dbg.space();
+}
--- /dev/null
+/***************************************************************************
+ * Copyright (C) 2005-2020 by the Quassel Project *
+ * devel@quassel-irc.org *
+ * *
+ * 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 2 of the License, or *
+ * (at your option) version 3. *
+ * *
+ * 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; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#pragma once
+
+#include <QAbstractSocket>
+#include <QByteArray>
+#include <QHostAddress>
+
+#include "common-export.h"
+
+struct COMMON_EXPORT ProxyLine
+{
+ QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::UnknownNetworkLayerProtocol;
+ QHostAddress sourceHost;
+ uint16_t sourcePort;
+ QHostAddress targetHost;
+ uint16_t targetPort;
+
+ static ProxyLine parseProxyLine(const QByteArray& line);
+ friend COMMON_EXPORT QDebug operator<<(QDebug dbg, const ProxyLine& p);
+};
{"ident-listen", tr("The address(es) quasselcore will listen on for ident requests. Same format as --listen."), tr("<address>[,...]"), "::1,127.0.0.1"},
{"oidentd", tr("Enable oidentd integration. In most cases you should also enable --strict-ident.")},
{"oidentd-conffile", tr("Set path to oidentd configuration file."), tr("file")},
+ {"proxy-cidr", tr("Set IP range from which proxy protocol definitions are allowed"), tr("<address>[,...]"), "::1,127.0.0.1"},
#ifdef HAVE_SSL
{"require-ssl", tr("Require SSL for remote (non-loopback) client connections.")},
{"ssl-cert", tr("Specify the path to the SSL certificate."), tr("path"), "configdir/quasselCert.pem"},
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
+#include <utility>
+
+#include <QtEndian>
+
#include <QHostAddress>
#include <QTimer>
-#include <QtEndian>
#ifdef HAVE_SSL
# include <QSslSocket>
# include <QTcpSocket>
#endif
+#include "proxyline.h"
#include "remotepeer.h"
#include "util.h"
, _socket(socket)
, _compressor(new Compressor(socket, level, this))
, _signalProxy(nullptr)
+ , _proxyLine({})
+ , _useProxyLine(false)
, _heartBeatTimer(new QTimer(this))
, _heartBeatCount(0)
, _lag(0)
QString RemotePeer::description() const
{
- if (socket())
- return socket()->peerAddress().toString();
+ return address();
+}
- return QString();
+QHostAddress RemotePeer::hostAddress() const
+{
+ if (_useProxyLine) {
+ return _proxyLine.sourceHost;
+ }
+ else if (socket()) {
+ return socket()->peerAddress();
+ }
+
+ return {};
}
QString RemotePeer::address() const
{
- if (socket())
- return socket()->peerAddress().toString();
-
- return QString();
+ QHostAddress address = hostAddress();
+ if (address.isNull()) {
+ return {};
+ }
+ else {
+ return address.toString();
+ }
}
quint16 RemotePeer::port() const
{
- if (socket())
+ if (_useProxyLine) {
+ return _proxyLine.sourcePort;
+ }
+ else if (socket()) {
return socket()->peerPort();
+ }
return 0;
}
bool RemotePeer::isLocal() const
{
- if (socket()) {
- if (socket()->peerAddress() == QHostAddress::LocalHost || socket()->peerAddress() == QHostAddress::LocalHostIPv6)
- return true;
- }
- return false;
+ return hostAddress() == QHostAddress::LocalHost ||
+ hostAddress() == QHostAddress::LocalHostIPv6;
}
bool RemotePeer::isOpen() const
if (_msgSize == 0) {
if (_compressor->bytesAvailable() < 4)
return false;
- _compressor->read((char*)&_msgSize, 4);
+ _compressor->read((char*) &_msgSize, 4);
_msgSize = qFromBigEndian<quint32>(_msgSize);
if (_msgSize > maxMessageSize) {
dispatch(HeartBeat(QDateTime::currentDateTime().toUTC()));
++_heartBeatCount;
}
+
+void RemotePeer::setProxyLine(ProxyLine proxyLine)
+{
+ _proxyLine = std::move(proxyLine);
+
+ if (socket()) {
+ if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
+ QList<QString> subnets = Quassel::optionValue("proxy-cidr").split(",");
+ for (const QString& subnet : subnets) {
+ if (socket()->peerAddress().isInSubnet(QHostAddress::parseSubnet(subnet))) {
+ _useProxyLine = true;
+ return;
+ }
+ }
+ }
+ }
+ _useProxyLine = false;
+}
#include "compressor.h"
#include "peer.h"
#include "protocol.h"
+#include "proxyline.h"
#include "signalproxy.h"
class QTimer;
void setSignalProxy(SignalProxy* proxy) override;
+ void setProxyLine(ProxyLine proxyLine);
+
virtual QString protocolName() const = 0;
QString description() const override;
virtual quint16 enabledFeatures() const { return 0; }
QString address() const override;
+ QHostAddress hostAddress() const;
quint16 port() const override;
bool isOpen() const override;
QTcpSocket* _socket;
Compressor* _compressor;
SignalProxy* _signalProxy;
+ ProxyLine _proxyLine;
+ bool _useProxyLine;
QTimer* _heartBeatTimer;
int _heartBeatCount;
int _lag;
connect(handler, &AuthHandler::socketError, this, &Core::socketError);
connect(handler, &CoreAuthHandler::handshakeComplete, this, &Core::setupClientSession);
- qInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
+ qInfo() << qPrintable(tr("Client connected from")) << qPrintable(handler->hostAddress().toString());
if (!_configured) {
stopListening(tr("Closing server for basic setup."));
auto* handler = qobject_cast<CoreAuthHandler*>(sender());
Q_ASSERT(handler);
- qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
+ qInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->hostAddress().toString());
_connectingClients.remove(handler);
handler->deleteLater();
: AuthHandler(parent)
, _peer(nullptr)
, _metricsServer(Core::instance()->metricsServer())
+ , _proxyReceived(false)
+ , _proxyLine({})
+ , _useProxyLine(false)
, _magicReceived(false)
, _legacy(false)
, _clientRegistered(false)
void CoreAuthHandler::onReadyRead()
{
- if (socket()->bytesAvailable() < 4)
- return;
-
// once we have selected a peer, we certainly don't want to read more data!
if (_peer)
return;
+ if (!_proxyReceived) {
+ quint32 magic;
+ socket()->peek((char*) &magic, 4);
+ magic = qFromBigEndian<quint32>(magic);
+
+ if (magic == Protocol::proxyMagic) {
+ if (!socket()->canReadLine()) {
+ return;
+ }
+ QByteArray line = socket()->readLine(108);
+ _proxyLine = ProxyLine::parseProxyLine(line);
+ if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
+ QList<QString> subnets = Quassel::optionValue("proxy-cidr").split(",");
+ for (const QString& subnet : subnets) {
+ if (socket()->peerAddress().isInSubnet(QHostAddress::parseSubnet(subnet))) {
+ _useProxyLine = true;
+ break;
+ }
+ }
+ }
+ }
+ _proxyReceived = true;
+ }
+
+ if (socket()->bytesAvailable() < 4)
+ return;
+
if (!_magicReceived) {
quint32 magic;
socket()->peek((char*)&magic, 4);
RemotePeer* peer = PeerFactory::createPeer(_supportedProtos, this, socket(), level, this);
if (!peer) {
- qWarning() << "Received invalid handshake data from client" << socket()->peerAddress().toString();
+ qWarning() << "Received invalid handshake data from client" << hostAddress().toString();
close();
return;
}
qDebug().nospace() << "Using " << qPrintable(peer->protocolName()) << "...";
_peer = peer;
+ if (_proxyLine.protocol != QAbstractSocket::UnknownNetworkLayerProtocol) {
+ _peer->setProxyLine(_proxyLine);
+ }
disconnect(socket(), &QIODevice::readyRead, this, &CoreAuthHandler::onReadyRead);
}
bool CoreAuthHandler::checkClientRegistered()
{
if (!_clientRegistered) {
- qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString())
+ qWarning() << qPrintable(tr("Client")) << qPrintable(hostAddress().toString())
<< qPrintable(tr("did not send a registration message before trying to login, rejecting."));
_peer->dispatch(
Protocol::ClientDenied(tr("<b>Client not initialized!</b><br>You need to send a registration message before trying to login.")));
useSsl = _connectionFeatures & Protocol::Encryption;
if (Quassel::isOptionSet("require-ssl") && !useSsl && !_peer->isLocal()) {
- qInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(socket()->peerAddress().toString()));
+ qInfo() << qPrintable(tr("SSL required but non-SSL connection attempt from %1").arg(hostAddress().toString()));
_peer->dispatch(Protocol::ClientDenied(tr("<b>SSL is required!</b><br>You need to use SSL in order to connect to this core.")));
_peer->close();
return;
return;
if (!Core::isConfigured()) {
- qWarning() << qPrintable(tr("Client")) << qPrintable(socket()->peerAddress().toString())
+ qWarning() << qPrintable(tr("Client")) << qPrintable(hostAddress().toString())
<< qPrintable(tr("attempted to login before the core was configured, rejecting."));
_peer->dispatch(Protocol::ClientDenied(
tr("<b>Attempted to login before core was configured!</b><br>The core must be configured before attempting to login.")));
}
if (uid == 0) {
- qInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(socket()->peerAddress().toString(), msg.user));
+ qInfo() << qPrintable(tr("Invalid login attempt from %1 as \"%2\"").arg(hostAddress().toString(), msg.user));
_peer->dispatch(Protocol::LoginFailed(tr(
"<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.")));
if (_metricsServer) {
}
qInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).")
- .arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
+ .arg(_peer->address(), msg.user, QString::number(uid.toInt())));
const auto& clientFeatures = _peer->features();
auto unsupported = clientFeatures.toStringList(false);
emit handshakeComplete(_peer, uid);
}
+QHostAddress CoreAuthHandler::hostAddress() const
+{
+ if (_useProxyLine) {
+ return _proxyLine.sourceHost;
+ }
+ else if (socket()) {
+ return socket()->peerAddress();
+ }
+
+ return {};
+}
+
+bool CoreAuthHandler::isLocal() const
+{
+ return hostAddress() == QHostAddress::LocalHost ||
+ hostAddress() == QHostAddress::LocalHostIPv6;
+}
+
/*** SSL Stuff ***/
void CoreAuthHandler::startSsl()
#include "authhandler.h"
#include "metricsserver.h"
#include "peerfactory.h"
+#include "proxyline.h"
#include "remotepeer.h"
#include "types.h"
public:
CoreAuthHandler(QTcpSocket* socket, QObject* parent = nullptr);
+ QHostAddress hostAddress() const;
+ bool isLocal() const override;
+
signals:
void handshakeComplete(RemotePeer* peer, UserId uid);
RemotePeer* _peer;
MetricsServer* _metricsServer;
+ bool _proxyReceived;
+ ProxyLine _proxyLine;
+ bool _useProxyLine;
bool _magicReceived;
bool _legacy;
bool _clientRegistered;