1 /**
2  * This file is part of TelepathyQt
3  *
4  * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/>
5  * @license LGPL 2.1
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include <TelepathyQt/IncomingStreamTubeChannel>
23 
24 #include "TelepathyQt/_gen/incoming-stream-tube-channel.moc.hpp"
25 
26 #include "TelepathyQt/types-internal.h"
27 #include "TelepathyQt/debug-internal.h"
28 
29 #include <TelepathyQt/PendingFailure>
30 #include <TelepathyQt/PendingStreamTubeConnection>
31 #include <TelepathyQt/PendingVariant>
32 #include <TelepathyQt/Types>
33 
34 #include <QHostAddress>
35 
36 namespace Tp
37 {
38 
39 struct TP_QT_NO_EXPORT IncomingStreamTubeChannel::Private
40 {
41     Private(IncomingStreamTubeChannel *parent);
42 
43     // Public object
44     IncomingStreamTubeChannel *parent;
45     static bool initRandom;
46 };
47 
48 bool IncomingStreamTubeChannel::Private::initRandom = true;
49 
Private(IncomingStreamTubeChannel * parent)50 IncomingStreamTubeChannel::Private::Private(IncomingStreamTubeChannel *parent)
51     : parent(parent)
52 {
53 }
54 
55 /**
56  * \class IncomingStreamTubeChannel
57  * \ingroup clientchannel
58  * \headerfile TelepathyQt/incoming-stream-tube-channel.h <TelepathyQt/IncomingStreamTubeChannel>
59  *
60  * \brief The IncomingStreamTubeChannel class represents an incoming Telepathy channel
61  * of type StreamTube.
62  *
63  * In particular, this class is meant to be used as a comfortable way for
64  * accepting incoming stream tubes. Tubes can be accepted as TCP and/or Unix sockets with various
65  * access control methods depending on what the service supports using the various overloads of
66  * acceptTubeAsTcpSocket() and acceptTubeAsUnixSocket().
67  *
68  * Once a tube is successfully accepted and open (the PendingStreamTubeConnection returned from the
69  * accepting methods has finished), the application can connect to the socket the address of which
70  * can be retrieved from PendingStreamTubeConnection::ipAddress() and/or
71  * PendingStreamTubeConnection::localAddress() depending on which accepting method was used.
72  * Connecting to this socket will open a tunneled connection to the service listening at the
73  * offering end of the tube.
74  *
75  * For more details, please refer to \telepathy_spec.
76  *
77  * See \ref async_model, \ref shared_ptr
78  */
79 
80 /**
81  * Feature representing the core that needs to become ready to make the
82  * IncomingStreamTubeChannel object usable.
83  *
84  * This is currently the same as StreamTubeChannel::FeatureCore, but may change to include more.
85  *
86  * When calling isReady(), becomeReady(), this feature is implicitly added
87  * to the requested features.
88  */
89 const Feature IncomingStreamTubeChannel::FeatureCore =
90     Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); // ST::FeatureCore
91 
92 /**
93  * Create a new IncomingStreamTubeChannel object.
94  *
95  * \param connection Connection owning this channel, and specifying the
96  *                   service.
97  * \param objectPath The channel object path.
98  * \param immutableProperties The channel immutable properties.
99  * \return A IncomingStreamTubeChannelPtr object pointing to the newly created
100  *         IncomingStreamTubeChannel object.
101  */
create(const ConnectionPtr & connection,const QString & objectPath,const QVariantMap & immutableProperties)102 IncomingStreamTubeChannelPtr IncomingStreamTubeChannel::create(const ConnectionPtr &connection,
103         const QString &objectPath, const QVariantMap &immutableProperties)
104 {
105     return IncomingStreamTubeChannelPtr(new IncomingStreamTubeChannel(connection, objectPath,
106             immutableProperties, IncomingStreamTubeChannel::FeatureCore));
107 }
108 
109 /**
110  * Construct a new IncomingStreamTubeChannel object.
111  *
112  * \param connection Connection owning this channel, and specifying the
113  *                   service.
114  * \param objectPath The channel object path.
115  * \param immutableProperties The channel immutable properties.
116  * \param coreFeature The core feature of the channel type, if any. The corresponding introspectable should
117  *                    depend on IncomingStreamTubeChannel::FeatureCore.
118  */
IncomingStreamTubeChannel(const ConnectionPtr & connection,const QString & objectPath,const QVariantMap & immutableProperties,const Feature & coreFeature)119 IncomingStreamTubeChannel::IncomingStreamTubeChannel(const ConnectionPtr &connection,
120         const QString &objectPath,
121         const QVariantMap &immutableProperties,
122         const Feature &coreFeature)
123     : StreamTubeChannel(connection, objectPath,
124             immutableProperties, coreFeature),
125       mPriv(new Private(this))
126 {
127 }
128 
129 /**
130  * Class destructor.
131  */
~IncomingStreamTubeChannel()132 IncomingStreamTubeChannel::~IncomingStreamTubeChannel()
133 {
134     delete mPriv;
135 }
136 
137 /**
138  * Accept an incoming stream tube as a TCP socket.
139  *
140  * This method accepts an incoming connection request for a stream tube. It can be called
141  * only if the tube is in the #TubeStateLocalPending state.
142  *
143  * The connection manager will open a TCP socket for the application to connect to. The address of
144  * the socket will be returned in PendingStreamTubeConnection::ipAddress() once the operation has
145  * finished successfully.
146  *
147  * This overload lets you specify an allowed address/port combination for connecting to the CM
148  * socket. Connections with other source addresses won't be accepted. The accessors
149  * supportsIPv4SocketsWithSpecifiedAddress() and supportsIPv6SocketsWithSpecifiedAddress() can be
150  * used to verify that the connection manager supports this kind of access control; otherwise, this
151  * method will always fail unless QHostAddress::Any (or QHostAddress::AnyIPv4 in Qt5) or
152  * QHostAddress::AnyIPv6 is passed, in which case the behavior is identical to the always supported
153  * acceptTubeAsTcpSocket() overload.
154  *
155  * Note that when using QHostAddress::Any (or QHostAddress::AnyIPv4 in Qt5) or
156  * QHostAddress::AnyIPv6, \a allowedPort is ignored.
157  *
158  * This method requires IncomingStreamTubeChannel::FeatureCore to be ready.
159  *
160  * \param allowedAddress An allowed address for connecting to the socket.
161  * \param allowedPort An allowed port for connecting to the socket.
162  * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished
163  *         when the stream tube is ready to be used
164  *         (hence in the #TubeStateOpen state).
165  */
acceptTubeAsTcpSocket(const QHostAddress & allowedAddress,quint16 allowedPort)166 PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsTcpSocket(
167         const QHostAddress &allowedAddress,
168         quint16 allowedPort)
169 {
170     if (!isReady(IncomingStreamTubeChannel::FeatureCore)) {
171         warning() << "IncomingStreamTubeChannel::FeatureCore must be ready before "
172                 "calling acceptTubeAsTcpSocket";
173         return new PendingStreamTubeConnection(TP_QT_ERROR_NOT_AVAILABLE,
174                 QLatin1String("Channel not ready"),
175                 IncomingStreamTubeChannelPtr(this));
176     }
177 
178     // The tube must be in local pending state
179     if (state() != TubeChannelStateLocalPending) {
180         warning() << "You can accept tubes only when they are in LocalPending state";
181         return new PendingStreamTubeConnection(TP_QT_ERROR_NOT_AVAILABLE,
182                 QLatin1String("Channel not ready"),
183                 IncomingStreamTubeChannelPtr(this));
184     }
185 
186     QVariant controlParameter;
187     SocketAccessControl accessControl;
188     QHostAddress hostAddress = allowedAddress;
189 
190 #if QT_VERSION >= 0x050000
191     if (hostAddress == QHostAddress::Any) {
192         hostAddress = QHostAddress::AnyIPv4;
193     }
194 #endif
195 
196     // Now, let's check what we need to do with accessControl. There is just one special case, Port.
197     if (hostAddress != QHostAddress::Any &&
198 #if QT_VERSION >= 0x050000
199         hostAddress != QHostAddress::AnyIPv4 &&
200 #endif
201         hostAddress != QHostAddress::AnyIPv6) {
202         // We need to have a valid QHostAddress AND Port.
203         if (hostAddress.isNull() || allowedPort == 0) {
204             warning() << "You have to set a valid allowed address+port to use Port access control";
205             return new PendingStreamTubeConnection(TP_QT_ERROR_INVALID_ARGUMENT,
206                     QLatin1String("The supplied allowed address and/or port was invalid"),
207                     IncomingStreamTubeChannelPtr(this));
208         }
209 
210         accessControl = SocketAccessControlPort;
211 
212         // IPv4 or IPv6?
213         if (hostAddress.protocol() == QAbstractSocket::IPv4Protocol) {
214             // IPv4 case
215             SocketAddressIPv4 addr;
216             addr.address = hostAddress.toString();
217             addr.port = allowedPort;
218 
219             controlParameter = QVariant::fromValue(addr);
220         } else if (hostAddress.protocol() == QAbstractSocket::IPv6Protocol) {
221             // IPv6 case
222             SocketAddressIPv6 addr;
223             addr.address = hostAddress.toString();
224             addr.port = allowedPort;
225 
226             controlParameter = QVariant::fromValue(addr);
227         } else {
228             // We're handling an IPv4/IPv6 socket only
229             warning() << "acceptTubeAsTcpSocket can be called only with a QHostAddress "
230                     "representing an IPv4 or IPv6 address";
231             return new PendingStreamTubeConnection(TP_QT_ERROR_INVALID_ARGUMENT,
232                     QLatin1String("Invalid host given"),
233                     IncomingStreamTubeChannelPtr(this));
234         }
235     } else {
236         // We have to do no special stuff here
237         accessControl = SocketAccessControlLocalhost;
238         // Since QDBusMarshaller does not like null variants, just add an empty string.
239         controlParameter = QVariant(QString());
240     }
241 
242     // Set the correct address type and access control
243     setAddressType(hostAddress.protocol() == QAbstractSocket::IPv4Protocol ?
244             SocketAddressTypeIPv4 :
245             SocketAddressTypeIPv6);
246     setAccessControl(accessControl);
247 
248     // Fail early if the combination is not supported
249     if ((accessControl == SocketAccessControlLocalhost &&
250             addressType() == SocketAddressTypeIPv4 &&
251             !supportsIPv4SocketsOnLocalhost()) ||
252         (accessControl == SocketAccessControlPort &&
253             addressType() == SocketAddressTypeIPv4 &&
254             !supportsIPv4SocketsWithSpecifiedAddress()) ||
255         (accessControl == SocketAccessControlLocalhost &&
256             addressType() == SocketAddressTypeIPv6 &&
257             !supportsIPv6SocketsOnLocalhost()) ||
258         (accessControl == SocketAccessControlPort &&
259             addressType() == SocketAddressTypeIPv6 &&
260             !supportsIPv6SocketsWithSpecifiedAddress())) {
261         warning() << "You requested an address type/access control combination "
262                 "not supported by this channel";
263         return new PendingStreamTubeConnection(TP_QT_ERROR_NOT_IMPLEMENTED,
264                 QLatin1String("The requested address type/access control "
265                               "combination is not supported"),
266                 IncomingStreamTubeChannelPtr(this));
267     }
268 
269     // Perform the actual call
270     PendingVariant *pv = new PendingVariant(
271             interface<Client::ChannelTypeStreamTubeInterface>()->Accept(
272                     addressType(),
273                     accessControl,
274                     QDBusVariant(controlParameter)),
275             IncomingStreamTubeChannelPtr(this));
276 
277     PendingStreamTubeConnection *op = new PendingStreamTubeConnection(pv, addressType(),
278             false, 0, IncomingStreamTubeChannelPtr(this));
279     return op;
280 }
281 
282 /**
283  * Accept an incoming stream tube as a TCP socket.
284  *
285  * This method accepts an incoming connection request for a stream tube. It can be called
286  * only if the tube is in the #TubeStateLocalPending state.
287  *
288  * The connection manager will open a TCP socket for the application to connect to. The address of
289  * the socket will be returned in PendingStreamTubeConnection::ipAddress() once the operation has
290  * finished successfully.
291  *
292  * Using this overload, the connection manager will accept every incoming connection from localhost.
293  *
294  * This accept method must be supported by all connection managers adhering to the \telepathy_spec.
295  *
296  * This method requires IncomingStreamTubeChannel::FeatureCore to be ready.
297  *
298  * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished
299  *         when the stream tube is ready to be used
300  *         (hence in the #TubeStateOpen state).
301  */
acceptTubeAsTcpSocket()302 PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsTcpSocket()
303 {
304     return acceptTubeAsTcpSocket(QHostAddress::Any, 0);
305 }
306 
307 /**
308  * Accept an incoming stream tube as a Unix socket.
309  *
310  * This method accepts an incoming connection request for a stream tube. It can be called
311  * only if the tube is in the #TubeStateLocalPending state.
312  *
313  * An Unix socket (can be used with QLocalSocket or alike) will be opened by the connection manager
314  * as the local tube endpoint. This is only supported if supportsUnixSocketsOnLocalhost() is \c
315  * true.
316  *
317  * You can also specify whether the CM should require an SCM_CREDS or SCM_CREDENTIALS message
318  * upon connection instead of accepting every incoming connection from localhost. This provides
319  * additional security, but requires sending the byte retrieved from
320  * PendingStreamTubeConnection::credentialByte() in-line in the socket byte stream (in a credentials
321  * message if available on the platform), which might not be compatible with all protocols or
322  * libraries. Also, only connection managers for which supportsUnixSocketsWithCredentials() is \c
323  * true support this type of access control.
324  *
325  * This method requires IncomingStreamTubeChannel::FeatureCore to be ready.
326  *
327  * \param requireCredentials Whether the CM should require an SCM_CREDS or SCM_CREDENTIALS message
328  *                           upon connection.
329  * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished
330  *         when the stream tube is ready to be used
331  *         (hence in the #TubeStateOpen state).
332  * \sa StreamTubeChannel::supportsUnixSocketsOnLocalhost(),
333  *     StreamTubeChannel::supportsUnixSocketsWithCredentials(),
334  *     StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost(),
335  *     StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials()
336  */
acceptTubeAsUnixSocket(bool requireCredentials)337 PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsUnixSocket(
338         bool requireCredentials)
339 {
340     if (!isReady(IncomingStreamTubeChannel::FeatureCore)) {
341         warning() << "IncomingStreamTubeChannel::FeatureCore must be ready before "
342                 "calling acceptTubeAsUnixSocket";
343         return new PendingStreamTubeConnection(TP_QT_ERROR_NOT_AVAILABLE,
344                 QLatin1String("Channel not ready"),
345                 IncomingStreamTubeChannelPtr(this));
346     }
347 
348     // The tube must be in local pending state
349     if (state() != TubeChannelStateLocalPending) {
350         warning() << "You can accept tubes only when they are in LocalPending state";
351         return new PendingStreamTubeConnection(TP_QT_ERROR_NOT_AVAILABLE,
352                 QLatin1String("Channel not ready"),
353                 IncomingStreamTubeChannelPtr(this));
354     }
355 
356     SocketAccessControl accessControl = requireCredentials ?
357             SocketAccessControlCredentials :
358             SocketAccessControlLocalhost;
359     setAddressType(SocketAddressTypeUnix);
360     setAccessControl(accessControl);
361 
362     // Fail early if the combination is not supported
363     if ((accessControl == SocketAccessControlLocalhost &&
364             addressType() == SocketAddressTypeUnix &&
365             !supportsUnixSocketsOnLocalhost()) ||
366         (accessControl == SocketAccessControlCredentials &&
367             addressType() == SocketAddressTypeUnix &&
368             !supportsUnixSocketsWithCredentials()) ||
369         (accessControl == SocketAccessControlLocalhost &&
370             addressType() == SocketAddressTypeAbstractUnix &&
371            !supportsAbstractUnixSocketsOnLocalhost()) ||
372         (accessControl == SocketAccessControlCredentials &&
373            addressType() == SocketAddressTypeAbstractUnix &&
374            !supportsAbstractUnixSocketsWithCredentials())) {
375         warning() << "You requested an address type/access control combination "
376                 "not supported by this channel";
377         return new PendingStreamTubeConnection(TP_QT_ERROR_NOT_IMPLEMENTED,
378                 QLatin1String("The requested address type/access control "
379                         "combination is not supported"),
380                 IncomingStreamTubeChannelPtr(this));
381     }
382 
383     QDBusVariant accessControlParam;
384     uchar credentialByte = 0;
385     if (accessControl == SocketAccessControlLocalhost) {
386         accessControlParam.setVariant(qVariantFromValue(static_cast<uint>(0)));
387     } else if (accessControl == SocketAccessControlCredentials) {
388         if (mPriv->initRandom) {
389             qsrand(QTime::currentTime().msec());
390             mPriv->initRandom = false;
391         }
392         credentialByte = static_cast<uchar>(qrand());
393         accessControlParam.setVariant(qVariantFromValue(credentialByte));
394     } else {
395         Q_ASSERT(false);
396     }
397 
398     // Perform the actual call
399     PendingVariant *pv = new PendingVariant(
400             interface<Client::ChannelTypeStreamTubeInterface>()->Accept(
401                     addressType(),
402                     accessControl,
403                     accessControlParam),
404             IncomingStreamTubeChannelPtr(this));
405 
406     PendingStreamTubeConnection *op = new PendingStreamTubeConnection(pv, addressType(),
407             requireCredentials, credentialByte, IncomingStreamTubeChannelPtr(this));
408     return op;
409 }
410 
onNewLocalConnection(uint connectionId)411 void IncomingStreamTubeChannel::onNewLocalConnection(uint connectionId)
412 {
413     addConnection(connectionId);
414 }
415 
416 }
417