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