diff options
Diffstat (limited to 'qt4/TelepathyQt4/stream-tube-client.cpp')
-rw-r--r-- | qt4/TelepathyQt4/stream-tube-client.cpp | 1048 |
1 files changed, 1048 insertions, 0 deletions
diff --git a/qt4/TelepathyQt4/stream-tube-client.cpp b/qt4/TelepathyQt4/stream-tube-client.cpp new file mode 100644 index 000000000..4830608a8 --- /dev/null +++ b/qt4/TelepathyQt4/stream-tube-client.cpp @@ -0,0 +1,1048 @@ +/** + * This file is part of TelepathyQt4 + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt4/StreamTubeClient> + +#include "TelepathyQt4/stream-tube-client-internal.h" +#include "TelepathyQt4/_gen/stream-tube-client.moc.hpp" +#include "TelepathyQt4/_gen/stream-tube-client-internal.moc.hpp" + +#include "TelepathyQt4/debug-internal.h" +#include "TelepathyQt4/simple-stream-tube-handler.h" + +#include <TelepathyQt4/AccountManager> +#include <TelepathyQt4/ClientRegistrar> +#include <TelepathyQt4/IncomingStreamTubeChannel> +#include <TelepathyQt4/PendingStreamTubeConnection> +#include <TelepathyQt4/StreamTubeChannel> + +#include <QAbstractSocket> +#include <QHash> + +namespace Tp +{ + +/** + * \class StreamTubeClient::TcpSourceAddressGenerator + * \ingroup serverclient + * \headerfile TelepathyQt4/stream-tube-client.h <TelepathyQt4/StreamTubeClient> + * + * \brief The StreamTubeClient::TcpSourceAddressGenerator abstract interface allows using socket + * source address/port based access control for connecting to tubes accepted as TCP sockets. + * + * By default, every application on the local computer is allowed to connect to the socket created + * by the protocol backend as the local endpoint of the tube. This is not always desirable, as that + * includes even other users. + * + * Note that since every TCP connection must have an unique source address, only one simultaneous + * connection can be made through each tube for which this type of access control has been used. + */ + +/** + * \fn QPair<QHostAddress, quint16> StreamTubeClient::TcpSourceAddressGenerator::nextSourceAddress(const AccountPtr &, const + * IncomingStreamTubeChannelPtr &) + * + * Return the source address from which connections will be allowed to the given \a tube once it has + * been accepted. + * + * Returning the pair (QHostAddress::Any, 0) makes the protocol backend allow connections from any + * address on the local computer. This can be used on a tube-by-tube basis if for some tubes its + * known that multiple connections need to be made, so a single source address doesn't suffice. + * + * The \a account and \a tube parameters can be inspected to make the decision; typically by looking + * at the tube's service type, parameters and/or initiator contact. + * + * The general pattern for implementing this method is: + * <ol> + * <li>Determine whether \a tube needs to allow multiple connections, and if so, skip source address + * access control completely</li> + * <li>Otherwise, create a socket and bind it to a free address</li> + * <li>Return this socket's address</li> + * <li>Keep the socket bound so that no other process can (accidentally or maliciously) take the + * address until it's used to connect to the tube when StreamTubeClient::tubeAcceptedAsTcp() is + * emitted for the tube</li> + * </ol> + * + * \param account The account from which the tube originates. + * \param tube The tube channel which is going to be accepted by the StreamTubeClient. + * + * \return A pair containing the host address and port allowed to connect. + */ + +/** + * \fn StreamTubeClient::TcpSourceAddressGenerator::~TcpSourceAddressGenerator + * + * Class destructor. Protected, because StreamTubeClient never deletes a TcpSourceAddressGenerator passed + * to it. + */ + +struct TELEPATHY_QT4_NO_EXPORT StreamTubeClient::Tube::Private : public QSharedData +{ + // empty placeholder for now +}; + +/** + * \class StreamTubeClient::Tube + * \ingroup serverclient + * \headerfile TelepathyQt4/stream-tube-client.h <TelepathyQt4/StreamTubeClient> + * + * \brief The StreamTubeClient::Tube class represents a tube being handled by the client. + */ + +/** + * Constructs a new invalid Tube instance. + */ +StreamTubeClient::Tube::Tube() +{ + // invalid instance +} + +/** + * Constructs a Tube instance for the given tube \a channel from the given \a account. + * + * \param account A pointer to the account the online connection of which the tube originates from. + * \param channel A pointer to the tube channel object. + */ +StreamTubeClient::Tube::Tube( + const AccountPtr &account, + const IncomingStreamTubeChannelPtr &channel) + : QPair<AccountPtr, IncomingStreamTubeChannelPtr>(account, channel), mPriv(new Private) +{ +} + +/** + * Copy constructor. + */ +StreamTubeClient::Tube::Tube( + const Tube &other) + : QPair<AccountPtr, IncomingStreamTubeChannelPtr>(other.account(), other.channel()), + mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +StreamTubeClient::Tube::~Tube() +{ + // mPriv deleted automatically +} + +/** + * Assignment operator. + */ +StreamTubeClient::Tube &StreamTubeClient::Tube::operator=( + const Tube &other) +{ + if (&other == this) { + return *this; + } + + first = other.account(); + second = other.channel(); + mPriv = other.mPriv; + + return *this; +} + +/** + * \fn bool StreamTubeClient::Tube::isValid() const + * + * Return whether or not the tube is valid or is just the null object created using the default + * constructor. + * + * \return \c true if valid, \c false otherwise. + */ + +/** + * \fn AccountPtr StreamTubeClient::Tube::account() const + * + * Return the account from which the tube originates. + * + * \return A pointer to the account object. + */ + +/** + * \fn IncomingStreamTubeChannelPtr StreamTubeClient::Tube::channel() const + * + * Return the actual tube channel. + * + * \return A pointer to the channel. + */ + +struct TELEPATHY_QT4_NO_EXPORT StreamTubeClient::Private +{ + Private(const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &maybeClientName, + bool monitorConnections, + bool bypassApproval) + : registrar(registrar), + handler(SimpleStreamTubeHandler::create( + p2pServices, roomServices, false, monitorConnections, bypassApproval)), + clientName(maybeClientName), + isRegistered(false), + acceptsAsTcp(false), acceptsAsUnix(false), + tcpGenerator(0), requireCredentials(false) + { + if (clientName.isEmpty()) { + clientName = QString::fromLatin1("TpQt4STubeClient_%1_%2") + .arg(registrar->dbusConnection().baseService() + .replace(QLatin1Char(':'), QLatin1Char('_')) + .replace(QLatin1Char('.'), QLatin1Char('_'))) + .arg((intptr_t) this, 0, 16); + } + } + + void ensureRegistered() + { + if (isRegistered) { + return; + } + + debug() << "Register StreamTubeClient with name " << clientName; + + if (registrar->registerClient(handler, clientName)) { + isRegistered = true; + } else { + warning() << "StreamTubeClient" << clientName + << "registration failed"; + } + } + + ClientRegistrarPtr registrar; + SharedPtr<SimpleStreamTubeHandler> handler; + QString clientName; + bool isRegistered; + + bool acceptsAsTcp, acceptsAsUnix; + TcpSourceAddressGenerator *tcpGenerator; + bool requireCredentials; + + QHash<StreamTubeChannelPtr, TubeWrapper *> tubes; +}; + +StreamTubeClient::TubeWrapper::TubeWrapper( + const AccountPtr &acc, + const IncomingStreamTubeChannelPtr &tube, + const QHostAddress &sourceAddress, + quint16 sourcePort, + StreamTubeClient *parent) + : QObject(parent), mAcc(acc), mTube(tube), mSourceAddress(sourceAddress), mSourcePort(sourcePort) +{ + if (sourcePort != 0) { + if ((sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && + !tube->supportsIPv4SocketsWithSpecifiedAddress()) || + (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol && + !tube->supportsIPv6SocketsWithSpecifiedAddress())) { + debug() << "StreamTubeClient falling back to Localhost AC for tube" << + tube->objectPath(); + mSourceAddress = sourceAddress.protocol() == QAbstractSocket::IPv4Protocol ? + QHostAddress::Any : QHostAddress::AnyIPv6; + mSourcePort = 0; + } + } + + connect(tube->acceptTubeAsTcpSocket(mSourceAddress, mSourcePort), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onTubeAccepted(Tp::PendingOperation*))); + connect(tube.data(), + SIGNAL(newConnection(uint)), + SLOT(onNewConnection(uint))); + connect(tube.data(), + SIGNAL(connectionClosed(uint,QString,QString)), + SLOT(onConnectionClosed(uint,QString,QString))); +} + +StreamTubeClient::TubeWrapper::TubeWrapper( + const AccountPtr &acc, + const IncomingStreamTubeChannelPtr &tube, + bool requireCredentials, + StreamTubeClient *parent) + : QObject(parent), mAcc(acc), mTube(tube), mSourcePort(0) +{ + if (requireCredentials && !tube->supportsUnixSocketsWithCredentials()) { + debug() << "StreamTubeClient falling back to Localhost AC for tube" << tube->objectPath(); + requireCredentials = false; + } + + connect(tube->acceptTubeAsUnixSocket(requireCredentials), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onTubeAccepted(Tp::PendingOperation*))); + connect(tube.data(), + SIGNAL(newConnection(uint)), + SLOT(onNewConnection(uint))); + connect(tube.data(), + SIGNAL(connectionClosed(uint,QString,QString)), + SLOT(onConnectionClosed(uint,QString,QString))); +} + +void StreamTubeClient::TubeWrapper::onTubeAccepted(Tp::PendingOperation *op) +{ + emit acceptFinished(this, qobject_cast<Tp::PendingStreamTubeConnection *>(op)); +} + +void StreamTubeClient::TubeWrapper::onNewConnection(uint conn) +{ + emit newConnection(this, conn); +} + +void StreamTubeClient::TubeWrapper::onConnectionClosed(uint conn, const QString &error, + const QString &message) +{ + emit connectionClosed(this, conn, error, message); +} + +/** + * \class StreamTubeClient + * \ingroup serverclient + * \headerfile TelepathyQt4/stream-tube-client.h <TelepathyQt4/StreamTubeClient> + * + * \brief The StreamTubeClient class is a Handler implementation for incoming %Stream %Tube channels, + * allowing an application to easily get notified about services they can connect to offered to them + * over Telepathy Tubes without worrying about the channel dispatching details. + * + * Telepathy Tubes is a technology for connecting arbitrary applications together through the IM + * network (and sometimes with direct peer-to-peer connections), such that issues like firewall/NAT + * traversal are automatically handled. Stream Tubes in particular offer properties similar to + * SOCK_STREAM sockets. The StreamTubeClient class negotiates tubes offered to us so that an + * application can connect such bytestream sockets of theirs to them. The StreamTubeServer class is + * the counterpart, offering services from a bytestream socket server to tubes requested to be + * initiated. + * + * Both peer-to-peer (\c TargetHandleType == \ref HandleTypeContact) and group (\c TargetHandleType + * == \ref HandleTypeRoom) channels are supported, and it's possible to specify the tube services to + * handle for each separately. There must be at least one service in total declared, as it never + * makes sense to handle stream tubes without considering the protocol of the service offered + * through them. + * + * %Connection monitoring allows fine-grained error reporting for connections made through tubes, + * and observing connections being made and broken even if the application code running + * StreamTubeClient can't easily get this information from the code actually connecting through it. + * Such a setting might occur e.g. when a wrapper application is developed to connect some existing + * "black box" networked application through TCP tubes by launching it with the appropriate command + * line arguments or alike for accepted tubes. + * + * Enabling connection monitoring adds a small overhead and latency to handling each incoming tube + * and signaling each new incoming connection over them, though, so use it only when needed. + * + * A service activated Handler can be implemented using StreamTubeClient by passing a predefined \a + * clientName manually to the chosen create() method, and installing Telepathy \c .client and D-Bus + * \c .service files declaring the implemented tube services as channel classes and a path to the + * executable. If this is not needed, the \a clientName can be omitted, in which case a random + * unique client name is generated and used instead. However, then the tube client application must + * already be running for remote contacts to be able to offer services to us over tubes. + * + * Whether the Handler application implemented using StreamTubeClient is service activatable or not, + * incoming channels will typically first be given to an Approver, if there is one for tube services + * corresponding to the tube in question. Only if the Approver decides that the tube communication + * should be allowed (usually by asking the user), or if there is no matching Approver at all, is + * the channel given to the actual Handler tube client. This can be overridden by setting \a + * bypassApproval to \c true, which skips approval for the given services completely and directs + * them straight to the Handler. + * + * StreamTubeClient shares Account, Connection and Channel proxies and Contact objects with the + * rest of the application as long as a reference to the AccountManager, ClientRegistrar, or the + * factories used elsewhere is passed to the create() method. A stand-alone tube client Handler can + * get away without passing these however, or just passing select factories to make the desired + * features prepared and subclasses employed for these objects for their own convenience. + * + * Whichever method is used, the ChannelFactory (perhaps indirectly) given must construct + * IncomingStreamTubeChannel instances or subclasses thereof for all channel classes corresponding + * to the tube services the client should be able to connect to. This is the default; overriding it + * without obeying these constraints using ChannelFactory::setSubclassForIncomingStreamTubes() or + * the related methods for room tubes prevents StreamTubeClient from operating correctly. + * + * \todo Coin up a small Python script or alike to easily generate the .client and .service files. + * (fd.o #41614) + */ + +/** + * Create a new StreamTubeClient, which will register itself on the session bus using an internal + * ClientRegistrar and use the given factories. + * + * \param p2pServices Names of the tube services to accept on peer-to-peer tube channels. + * \param roomServices Names of the tube services to accept on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + */ +StreamTubeClientPtr StreamTubeClient::create( + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return create( + QDBusConnection::sessionBus(), + accountFactory, + connectionFactory, + channelFactory, + contactFactory, + p2pServices, + roomServices, + clientName, + monitorConnections, + bypassApproval); +} + +/** + * Create a new StreamTubeClient, which will register itself on the given \a bus using an internal + * ClientRegistrar and use the given factories. + * + * The factories must all be created for the given \a bus. + * + * \param bus Connection to the bus to register on. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + */ +StreamTubeClientPtr StreamTubeClient::create( + const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) +{ + return create( + ClientRegistrar::create( + bus, + accountFactory, + connectionFactory, + channelFactory, + contactFactory), + p2pServices, + roomServices, + clientName, + monitorConnections, + bypassApproval); +} + +/** + * Create a new StreamTubeClient, which will register itself on the bus of and share objects with + * the given \a accountManager, creating an internal ClientRegistrar. + * + * \param accountManager A pointer to the account manager to link up with. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + */ +StreamTubeClientPtr StreamTubeClient::create( + const AccountManagerPtr &accountManager, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) +{ + return create( + accountManager->dbusConnection(), + accountManager->accountFactory(), + accountManager->connectionFactory(), + accountManager->channelFactory(), + accountManager->contactFactory(), + p2pServices, + roomServices, + clientName, + monitorConnections, + bypassApproval); +} + +/** + * Create a new StreamTubeClient, which will register itself on the bus of and using the given + * client \a registrar, and share objects with it. + * + * \param registrar The client registrar to use. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + */ +StreamTubeClientPtr StreamTubeClient::create( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) +{ + if (p2pServices.isEmpty() && roomServices.isEmpty()) { + warning() << "Tried to create a StreamTubeClient with no services, returning NULL"; + return StreamTubeClientPtr(); + } + + return StreamTubeClientPtr( + new StreamTubeClient(registrar, p2pServices, roomServices, clientName, + monitorConnections, bypassApproval)); +} + +StreamTubeClient::StreamTubeClient( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) + : mPriv(new Private(registrar, p2pServices, roomServices, clientName, monitorConnections, bypassApproval)) +{ + connect(mPriv->handler.data(), + SIGNAL(invokedForTube( + Tp::AccountPtr, + Tp::StreamTubeChannelPtr, + QDateTime, + Tp::ChannelRequestHints)), + SLOT(onInvokedForTube( + Tp::AccountPtr, + Tp::StreamTubeChannelPtr, + QDateTime, + Tp::ChannelRequestHints))); +} + +/** + * Class destructor. + */ +StreamTubeClient::~StreamTubeClient() +{ + if (isRegistered()) { + mPriv->registrar->unregisterClient(mPriv->handler); + } + + delete mPriv; +} + +/** + * Return the client registrar used by the client to register itself as a Telepathy channel Handler + * %Client. + * + * This is the registrar originally passed to + * create(const ClientRegistrarPtr &, const QStringList &, const QStringList &, const QString &, bool, bool) + * if that was used, and an internally constructed one otherwise. In any case, it can be used to + * e.g. register further clients, just like any other ClientRegistrar. + * + * \return A pointer to the registrar. + */ +ClientRegistrarPtr StreamTubeClient::registrar() const +{ + return mPriv->registrar; +} + +/** + * Return the Telepathy %Client name of the client. + * + * \return The name, without the \c org.freedesktop.Telepathy.Client. prefix of the full D-Bus service name. + */ +QString StreamTubeClient::clientName() const +{ + return mPriv->clientName; +} + +/** + * Return whether the client has been successfully registered or not. + * + * Registration is attempted, at the latest, when the client is first set to accept incoming tubes, + * either as TCP sockets (setToAcceptAsTcp()) or Unix ones (setToAcceptAsUnix()). It can fail e.g. + * because the connection to the bus has failed, or a predefined \a clientName has been passed to + * create(), and a %Client with the same name is already registered. Typically, failure registering + * would be a fatal error for a stand-alone tube handler, but only a warning event for an + * application serving other purposes. In any case, a high-quality user of the API will check the + * return value of this accessor after choosing the desired address family. + * + * \return \c true if the client has been successfully registered, \c false if not. + */ +bool StreamTubeClient::isRegistered() const +{ + return mPriv->isRegistered; +} + +/** + * Return whether connection monitoring is enabled on this client. + * + * For technical reasons, connection monitoring can't be enabled when the client is already running, + * so there is no corresponding setter method. It has to be enabled by passing \c true as the \a + * monitorConnections parameter to the create() method. + * + * If connection monitoring isn't enabled, newConnection() and connectionClosed() won't be + * emitted and connections() won't be populated. + * + * \return \c true if monitoring is enabled, \c false if not. + */ +bool StreamTubeClient::monitorsConnections() const +{ + return mPriv->handler->monitorsConnections(); +} + +/** + * Return whether the client is currently set to accept incoming tubes as TCP sockets. + * + * \return \c true if the client will accept tubes as TCP sockets, \c false if it will accept them + * as Unix ones or hasn't been set to accept at all yet. + */ +bool StreamTubeClient::acceptsAsTcp() const +{ + return mPriv->acceptsAsTcp; +} + +/** + * Return the TCP source address generator, if any, set by setToAcceptAsTcp() previously. + * + * \return A pointer to the generator instance. + */ +StreamTubeClient::TcpSourceAddressGenerator *StreamTubeClient::tcpGenerator() const +{ + if (!acceptsAsTcp()) { + warning() << "StreamTubeClient::tcpGenerator() used, but not accepting as TCP, returning 0"; + return 0; + } + + return mPriv->tcpGenerator; +} + +/** + * Return whether the client is currently set to accept incoming tubes as Unix sockets. + * + * \return \c true if the client will accept tubes as Unix sockets, \c false if it will accept them + * as TCP ones or hasn't been set to accept at all yet. + */ +bool StreamTubeClient::acceptsAsUnix() const +{ + return mPriv->acceptsAsUnix; +} + +/** + * Set the client to accept tubes received to handle in the future in a fashion which will yield a + * TCP socket as the local endpoint to connect to. + * + * A source address generator can optionally be set. If non-null, it will be invoked for each new + * tube received to handle and an attempt is made to restrict connections to the tube's local socket + * endpoint to those from that source address. + * + * However, if the protocol backend doesn't actually support source address based access control, + * tubeAcceptedAsTcp() will be emitted with QHostAddress::Any as the allowed source address to + * signal that it doesn't matter where we connect from, but more importantly, that anybody else on + * the same host could have, and can, connect to the tube. The tube can be closed at this point if + * this would be unacceptable security-wise. To totally prevent the tube from being accepted in the + * first place, one can close it already when tubeOffered() is emitted for it - support for the + * needed security mechanism can be queried using its supportsIPv4SocketsWithSpecifiedAddress() + * accessor. + * + * The handler is registered on the bus at the latest when this method or setToAcceptAsUnix() is + * called for the first time, so one should check the return value of isRegistered() at that point + * to verify that was successful. + * + * \param generator A pointer to the source address generator to use, or 0 to allow all + * connections from the local host. + * + * \todo Make it possible to set the tube client to auto-close tubes if the desired access control + * level is not achieved, as an alternative to the current best-effort behavior. + */ +void StreamTubeClient::setToAcceptAsTcp(TcpSourceAddressGenerator *generator) +{ + mPriv->tcpGenerator = generator; + mPriv->acceptsAsTcp = true; + mPriv->acceptsAsUnix = false; + + mPriv->ensureRegistered(); +} + +/** + * Set the client to accept tubes received to handle in the future in a fashion which will yield a + * Unix socket as the local endpoint to connect to. + * + * If that doesn't cause problems for the payload protocol, it's possible to increase security by + * restricting the processes allowed to connect to the local endpoint socket to those from the same + * user ID as the protocol backend is running as by setting \a requireCredentials to \c true. This + * requires transmitting a single byte, signaled as the \a credentialByte parameter to the + * tubeAcceptedAsUnix() signal, in a \c SCM_CREDS or SCM_CREDENTIALS message, whichever is supported + * by the platform, as the first thing after having connected to the socket. Even if the platform + * doesn't implement either concept, the byte must still be sent. + * + * However, not all protocol backends support the credential passing based access control on all the + * platforms they can run on. If a tube is offered through such a backend, tubeAcceptedAsUnix() will + * be emitted with \a requiresCredentials set to \c false, to signal that a credential byte should + * NOT be sent for that tube, and that any local process can or could have connected to the tube + * already. The tube can be closed at this point if this would be unacceptable security-wise. To + * totally prevent the tube from being accepted in the first place, one can close it already when + * tubeOffered() is emitted for it - support for the needed security mechanism can be queried using + * its supportsIPv4SocketsWithSpecifiedAddress() + * accessor. + * + * The handler is registered on the bus at the latest when this method or setToAcceptAsTcp() is + * called for the first time, so one should check the return value of isRegistered() at that point + * to verify that was successful. + * + * \param requireCredentials \c true to try and restrict connecting by UID, \c false to allow all + * connections. + * + * \todo Make it possible to set the tube client to auto-close tubes if the desired access control + * level is not achieved, as an alternative to the current best-effort behavior. + */ +void StreamTubeClient::setToAcceptAsUnix(bool requireCredentials) +{ + mPriv->tcpGenerator = 0; + mPriv->acceptsAsTcp = false; + mPriv->acceptsAsUnix = true; + mPriv->requireCredentials = requireCredentials; + + mPriv->ensureRegistered(); +} + +/** + * Return the tubes currently handled by the client. + * + * \return A list of Tube structures containing pointers to the account and tube channel for each + * tube. + */ +QList<StreamTubeClient::Tube> StreamTubeClient::tubes() const +{ + QList<Tube> tubes; + + foreach (TubeWrapper *wrapper, mPriv->tubes.values()) { + tubes.push_back(Tube(wrapper->mAcc, wrapper->mTube)); + } + + return tubes; +} + +/** + * Return the ongoing connections established through tubes signaled by this client. + * + * The returned mapping has for each Tube a structure containing pointers to the account and tube + * channel objects as keys, with the integer identifiers for the current connections on them as the + * values. The IDs are unique amongst the connections active on a single tube at any given time, but + * not globally. + * + * This is effectively a state recovery accessor corresponding to the change notification signals + * newConnection() and connectionClosed(). + * + * The mapping is only populated if connection monitoring was requested when creating the client (so + * monitorsConnections() returns \c true). + * + * \return The connections in a mapping with Tube structures containing pointers to the account and + * channel objects for each tube as keys, and the sets of numerical IDs as values. + */ +QHash<StreamTubeClient::Tube, QSet<uint> > StreamTubeClient::connections() const +{ + QHash<Tube, QSet<uint> > conns; + if (!monitorsConnections()) { + warning() << "StreamTubeClient::connections() used, but connection monitoring is disabled"; + return conns; + } + + foreach (const Tube &tube, tubes()) { + if (!tube.channel()->isValid()) { + // The tube has been invalidated, so skip it to avoid warnings + // We're going to get rid of the wrapper in the next mainloop iteration when we get the + // invalidation signal + continue; + } + + QSet<uint> tubeConns = QSet<uint>::fromList(tube.channel()->connections()); + if (!tubeConns.empty()) { + conns.insert(tube, tubeConns); + } + } + + return conns; +} + +void StreamTubeClient::onInvokedForTube( + const AccountPtr &acc, + const StreamTubeChannelPtr &tube, + const QDateTime &time, + const ChannelRequestHints &hints) +{ + Q_ASSERT(isRegistered()); // our SSTH shouldn't be receiving any channels unless it's registered + Q_ASSERT(!tube->isRequested()); + Q_ASSERT(tube->isValid()); // SSTH won't emit invalid tubes + + if (mPriv->tubes.contains(tube)) { + debug() << "Ignoring StreamTubeClient reinvocation for tube" << tube->objectPath(); + return; + } + + IncomingStreamTubeChannelPtr incoming = IncomingStreamTubeChannelPtr::qObjectCast(tube); + + if (!incoming) { + warning() << "The ChannelFactory used by StreamTubeClient must construct" << + "IncomingStreamTubeChannel subclasses for Requested=false StreamTubes"; + tube->requestClose(); + return; + } + + TubeWrapper *wrapper = 0; + + if (mPriv->acceptsAsTcp) { + QPair<QHostAddress, quint16> srcAddr = + qMakePair(QHostAddress(QHostAddress::Any), quint16(0)); + + if (mPriv->tcpGenerator) { + srcAddr = mPriv->tcpGenerator->nextSourceAddress(acc, incoming); + } + + wrapper = new TubeWrapper(acc, incoming, srcAddr.first, srcAddr.second, this); + } else { + Q_ASSERT(mPriv->acceptsAsUnix); // we should only be registered when we're set to accept as either TCP or Unix + wrapper = new TubeWrapper(acc, incoming, mPriv->requireCredentials, this); + } + + connect(wrapper, + SIGNAL(acceptFinished(TubeWrapper*,Tp::PendingStreamTubeConnection*)), + SLOT(onAcceptFinished(TubeWrapper*,Tp::PendingStreamTubeConnection*))); + connect(tube.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString))); + + if (monitorsConnections()) { + connect(wrapper, + SIGNAL(newConnection(TubeWrapper*,uint)), + SLOT(onNewConnection(TubeWrapper*,uint))); + connect(wrapper, + SIGNAL(connectionClosed(TubeWrapper*,uint,QString,QString)), + SLOT(onConnectionClosed(TubeWrapper*,uint,QString,QString))); + } + + mPriv->tubes.insert(tube, wrapper); + + emit tubeOffered(acc, incoming); +} + +void StreamTubeClient::onAcceptFinished(TubeWrapper *wrapper, PendingStreamTubeConnection *conn) +{ + Q_ASSERT(wrapper != NULL); + Q_ASSERT(conn != NULL); + + if (!mPriv->tubes.contains(wrapper->mTube)) { + debug() << "StreamTubeClient ignoring Accept result for invalidated tube" + << wrapper->mTube->objectPath(); + return; + } + + if (conn->isError()) { + warning() << "StreamTubeClient couldn't accept tube" << wrapper->mTube->objectPath() << '-' + << conn->errorName() << ':' << conn->errorMessage(); + + if (wrapper->mTube->isValid()) { + wrapper->mTube->requestClose(); + } + + wrapper->mTube->disconnect(this); + emit tubeClosed(wrapper->mAcc, wrapper->mTube, conn->errorName(), conn->errorMessage()); + mPriv->tubes.remove(wrapper->mTube); + wrapper->deleteLater(); + return; + } + + debug() << "StreamTubeClient accepted tube" << wrapper->mTube->objectPath(); + + if (conn->addressType() == SocketAddressTypeIPv4 + || conn->addressType() == SocketAddressTypeIPv6) { + QPair<QHostAddress, quint16> addr = conn->ipAddress(); + emit tubeAcceptedAsTcp(addr.first, addr.second, wrapper->mSourceAddress, + wrapper->mSourcePort, wrapper->mAcc, wrapper->mTube); + } else { + emit tubeAcceptedAsUnix(conn->localAddress(), conn->requiresCredentials(), + conn->credentialByte(), wrapper->mAcc, wrapper->mTube); + } +} + +void StreamTubeClient::onTubeInvalidated(Tp::DBusProxy *proxy, const QString &error, + const QString &message) +{ + StreamTubeChannelPtr tube(qobject_cast<StreamTubeChannel *>(proxy)); + Q_ASSERT(!tube.isNull()); + + TubeWrapper *wrapper = mPriv->tubes.value(tube); + if (!wrapper) { + // Accept finish with error already removed it + return; + } + + debug() << "Client StreamTube" << tube->objectPath() << "invalidated - " << error << ':' + << message; + + emit tubeClosed(wrapper->mAcc, wrapper->mTube, error, message); + mPriv->tubes.remove(tube); + delete wrapper; +} + +void StreamTubeClient::onNewConnection( + TubeWrapper *wrapper, + uint conn) +{ + Q_ASSERT(monitorsConnections()); + emit newConnection(wrapper->mAcc, wrapper->mTube, conn); +} + +void StreamTubeClient::onConnectionClosed( + TubeWrapper *wrapper, + uint conn, + const QString &error, + const QString &message) +{ + Q_ASSERT(monitorsConnections()); + emit connectionClosed(wrapper->mAcc, wrapper->mTube, conn, error, message); +} + +/** + * \fn void StreamTubeClient::tubeOffered(const AccountPtr &account, const + * IncomingStreamTubeChannelPtr &tube) + * + * Emitted when one of the services we're interested in connecting to has been offered by us as a + * tube, which we've began handling. + * + * This is emitted before invoking the TcpSourceAddressGenerator, if any, for the tube. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the actual tube channel. + */ + +/** + * \fn void StreamTubeClient::tubeAcceptedAsTcp(const QHostAddress &listenAddress, quint16 + * listenPort, const QHostAddress &sourceAddress, quint16 sourcePort, const AccountPtr &account, + * const IncomingStreamTubeChannelPtr &tube) + * + * Emitted when a tube offered to us (previously announced with tubeOffered()) has been successfully + * accepted and a TCP socket established as the local endpoint, as specified by setToAcceptAsTcp(). + * + * The allowed source address and port are signaled here if there was a TcpSourceAddressGenerator set + * at the time of accepting this tube, it yielded a non-zero address, and the protocol backend + * supports source address based access control. This enables the application to use the correct one + * of the sockets it currently has bound to the generated addresses. + * + * \param listenAddress The listen address of the local endpoint socket. + * \param listenPort The listen port of the local endpoint socket. + * \param sourceAddress The host address allowed to connect to the tube, or QHostAddress::Any if + * source address based access control is not in use. + * \param sourcePort The port from which connections are allowed to the tube, or 0 if source address + * based access control is not in use. + * \param account A pointer to the account object through which the tube was offered. + * \param tube A pointer to the actual tube channel object. + */ + +// Explicitly specifying Tp:: for this signal's argument types works around a doxygen bug causing it +// to confuse the doc here being for StreamTubeServer::tubeClosed :/ +/** + * \fn void StreamTubeClient::tubeClosed(const Tp::AccountPtr &account, const + * Tp::IncomingStreamTubeChannelPtr &tube, const QString &error, const QString &message) + * + * Emitted when a tube we've been handling (previously announced with tubeOffered()) has + * encountered an error or has otherwise been closed from further communication. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the actual tube channel. + * \param error The D-Bus error name corresponding to the reason for the closure. + * \param message A freeform debug message associated with the error. + */ + +/** + * \fn void StreamTubeClient::tubeAcceptedAsUnix(const QHostAddress &listenAddress, quint16 + * listenPort, const QHostAddress &sourceAddress, quint16 sourcePort, const AccountPtr &account, + * const IncomingStreamTubeChannelPtr &tube) + * + * Emitted when a tube offered to us (previously announced with tubeOffered()) has been successfully + * accepted and a Unix socket established as the local endpoint, as specified by setToAcceptAsUnix(). + * + * The credential byte which should be sent after connecting to the tube (in a SCM_CREDENTIALS or + * SCM_CREDS message if supported by the platform) will be signaled here if the client was set to + * attempt requiring credentials at the time of accepting this tube, and the protocol backend + * supports credential passing based access control. Otherwise, \a requiresCredentials will be false + * and no byte or associated out-of-band credentials metadata should be sent. + * + * \param listenAddress The listen address of the local endpoint socket. + * \param requiresCredentials \c true if \a credentialByte should be sent after connecting to the + * socket, \c false if not. + * \param credentialByte The byte to send if \a requiresCredentials is \c true. + * \param account A pointer to the account object through which the tube was offered. + * \param tube A pointer to the actual tube channel object. + */ + +/** + * \fn void StreamTubeClient::newConnection(const AccountPtr &account, const + * IncomingStreamTubeChannelPtr &tube, uint connectionId) + * + * Emitted when a new connection has been made to the local endpoint socket for \a tube. + * + * This can be used to later associate connection errors reported by connectionClosed() with the + * corresponding application sockets. However, establishing the association generally requires + * connecting only one socket at a time, waiting for newConnection() to be emitted, and only then + * proceeding, as there is no identification for the connections unlike the incoming connections in + * StreamTubeServer. + * + * Note that the connection IDs are only unique within a given tube, so identification of the tube + * channel must also be recorded together with the ID to establish global uniqueness. Even then, the + * a connection ID can be reused after the previous connection identified by it having been + * signaled as closed with connectionClosed(). + * + * This is only emitted if connection monitoring was enabled when creating the StreamTubeClient. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the tube channel through which the connection has been made. + * \param connectionId The integer ID of the new connection. + */ + +/** + * \fn void StreamTubeClient::connectionClosed(const AccountPtr &account, const + * IncomingStreamTubeChannelPtr &tube, uint connectionId, const QString &error, const + * QString &message) + * + * Emitted when a connection (previously announced with newConnection()) through one of our + * handled tubes has been closed due to an error or by a graceful disconnect (in which case the + * error is ::TP_QT4_ERROR_CANCELLED). + * + * This is only emitted if connection monitoring was enabled when creating the StreamTubeClient. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the tube channel through which the connection had been made. + * \param connectionId The integer ID of the connection closed. + * \param error The D-Bus error name corresponding to the reason for the closure. + * \param message A freeform debug message associated with the error. + */ + +} // Tp |