1 /*
2   Copyright (c) 2007-2019 by Jakob Schröter <js@camaya.net>
3   This file is part of the gloox library. http://camaya.net/gloox
4 
5   This software is distributed under a license. The full license
6   agreement can be found in the file LICENSE in this distribution.
7   This software may not be copied, modified, sold or distributed
8   other than expressed in the named license agreement.
9 
10   This software is distributed without any warranty.
11 */
12 
13 
14 #ifndef SIPROFILEFT_H__
15 #define SIPROFILEFT_H__
16 
17 #include "iqhandler.h"
18 #include "socks5bytestreammanager.h"
19 #include "siprofilehandler.h"
20 #include "sihandler.h"
21 #include "simanager.h"
22 #include "bytestreamhandler.h"
23 
24 #include <string>
25 #include <map>
26 
27 namespace gloox
28 {
29 
30   class ClientBase;
31   class InBandBytestream;
32   class IQ;
33   class JID;
34   class SIProfileFTHandler;
35   class SOCKS5Bytestream;
36 
37   /**
38    * @brief An implementation of the file transfer SI profile (@xep{0096}).
39    *
40    * An SIProfileFT object acts as a 'plugin' to the SIManager. SIProfileFT
41    * manages most of the file transfer functionality. The naming comes from the fact that
42    * File Transfer (FT) is a profile of Stream Initiation (SI).
43    *
44    * Usage:
45    *
46    * Create a new SIProfileFT object. It needs a ClientBase -derived object (e.g. Client)
47    * as well as a SIProfileFTHandler -derived object that will receive file transfer-related events.
48    * If you already use SI and the SIManager somewhere else, you should pass a pointer to that
49    * SIManager object as third parameter to SIProfileFT's constructor.
50    * @code
51    * class MyFileTransferHandler : public SIProfileFTHandler
52    * {
53    *   // ...
54    * };
55    *
56    * Client* client = new Client( ... );
57    * // ...
58    * MyFileTransferHandler* mh = new MyFileTransferHandler( ... );
59    *
60    * SIProfileFT* ft = new SIProfileFT( client, mh );
61    * @endcode
62    *
63    * You are now, basically, ready to send and receive files.
64    *
65    * A couple of notes:
66    * @li There are two (actually two and a half) possible "techniques" to transfer files
67    * using SI. The first is using a peer-to-peer SOCKS5 bytestream, optionally via a
68    * (special) SOCKS5 proxy.
69    * The second techniques is using an in-band bytestream, i.e. the data is encapsulated in XMPP stanzas
70    * and sent through the server.
71    *
72    * @li To be able to send files using the former method (SOCKS5 bytestreams), you may need
73    * access to a SOCKS5 bytestream proxy (called StreamHost). This is especially true if either
74    * or both of sender and receiver are behind NATs or are otherwise blocked from establishing
75    * direct TCP connections. You should use Disco to query a potential SOCKS5 proxy
76    * for its host and port parameters and feed that information into SIProfileFT:
77    * @code
78    * ft->addStreamHost( JID( "proxy.server.dom" ), "101.102.103.104", 6677 );
79    * @endcode
80    * You should @b not hard-code this information (esp. host/IP and port) into your app since
81    * the proxy may go down occasionally or vanish completely.
82    *
83    * @li In addition to (or even instead of) using external SOCKS5 proxies, you can use a
84    * SOCKS5BytestreamServer object that gloox provides:
85    * @code
86    * SOCKS5BytestreamServer* server = new SOCKS5BytestreamServer( client->logInstance(), 1234 );
87    * if( server->listen() != ConnNoError )
88    *   printf( "port in use\n" );
89    *
90    * ft->addStreamHost( client->jid(), my_ip, 1234 );
91    * ft->registerSOCKS5BytestreamServer( server );
92    * @endcode
93    * This listening server should then be integrated into your mainloop to have its
94    * @link gloox::SOCKS5BytestreamServer::recv() recv() @endlink method called from time to time.
95    * It is safe to put the server into its own thread.
96    *
97    * @li When you finally receive a Bytestream via the SIProfileFTHandler, you will need
98    * to integrate this bytestream with your mainloop, or put it into a separate thread (if
99    * occasional blocking is not acceptable). You will need to call
100    * @link gloox::Bytestream::connect() connect() @endlink on that Bytestream. For SOCKS5 bytestreams,
101    * this function will try to connect to each of the given StreamHosts and block until it has established
102    * a connection with one of them (or until all attempts failed). Further, if you want to receive
103    * a file via the bytestream, you will have to call recv() on the object from time to time.
104    * For in-band bytestreams, @link gloox::Bytestream::connect() connect() @endlink will send an "open the
105    * bytestream" request to the contact.
106    *
107    * @li For both stream types,
108    * @link gloox::BytestreamDataHandler::handleBytestreamOpen() BytestreamDataHandler::handleBytestreamOpen() @endlink
109    * will announce the established bytestream. The stream then is ready to send and receive data.
110    *
111    * @li In general, both types of streams can be handled equally, i.e. there's no need to know whether
112    * the underlying stream really is a SOCKS5Bytestream or an InBandBytestream.
113    * @link gloox::Bytestream::type() Bytestream::type() @endlink tells anyway. Note, however, that
114    * sending large amounts of data using in-band bytestreams may trigger rate limiting in some servers.
115    *
116    * @li If you e.g. told Client to connect through a @link gloox::ConnectionHTTPProxy HTTP proxy @endlink
117    * or a @link gloox::ConnectionSOCKS5Proxy SOCKS5 proxy @endlink, or any other ConnectionBase -derived
118    * method, or even chains thereof, SIProfileFT will use the same connection types with the same
119    * configuration to connect to the Stream Host/SOCKS5 proxy. If this is inappropriate because you have
120    * e.g. a local SOCKS5 proxy inside your local network, use SOCKS5Bytestream::setConnectionImpl() to
121    * override the above default connection(s).
122    *
123    * @li Do @b not delete Bytestream objects manually. Use dispose() instead.
124    *
125    * @li When using the Client's JID as the first argument to addStreamHost() as in the code snippet
126    * above, make sure the JID is actually a full JID. If you let the server pick a resource, the call
127    * to Client::jid() needs to be made @b after the connection has been established and authenticated,
128    * because only then Client knows its full JID. This is generally a good idea, since the server
129    * may choose to change the resource, even if you provided one at login.
130    *
131    * @li The interal SOCKS5BytestreamServer will obviously not work across NATs.
132    *
133    * @li Using addStreamHost(), you can add as many potential StreamHosts as you like. However, you
134    * should add the best options (e.g. the local SOCKS5BytestreamServer) first.
135    *
136    * When cleaning up, delete the objectes you created above in the opposite order of
137    * creation:
138    *
139    * @code
140    * delete server
141    * delete ft;
142    * delete client;
143    * @endcode
144    *
145    * For usage examples see src/examples/ft_send.cpp and src/examples/ft_recv.cpp.
146    *
147    * @author Jakob Schröter <js@camaya.net>
148    * @since 0.9
149    */
150   class GLOOX_API SIProfileFT : public SIProfileHandler, public SIHandler,
151                                 public BytestreamHandler, public IqHandler
152   {
153     public:
154       /**
155        * Supported stream types.
156        */
157       enum StreamType
158       {
159         FTTypeS5B     = 1,          /**< SOCKS5 Bytestreams. */
160         FTTypeIBB     = 2,          /**< In-Band Bytestreams. */
161         FTTypeOOB     = 4,          /**< Out-of-Band Data. */
162         FTTypeAll     = 0xFF        /**< All types. */
163       };
164 
165       /**
166        * Constructor.
167        * @param parent The ClientBase to use for signaling.
168        * @param sipfth The SIProfileFTHandler to receive events.
169        * @param manager An optional SIManager to register with. If this is zero, SIProfileFT
170        * will create its own SIManager. You should pass a valid SIManager here if you are
171        * already using one with the @c parent ClientBase above.
172        * @param s5Manager An optional SOCKS5BytestreamManager to use. If this is zero, SIProfileFT
173        * will create its own SOCKS5BytestreamManager. You should pass a valid SOCKS5BytestreamManager
174        * here if you are already using one with the @c parent ClientBase above.
175        * @note If you passed a SIManager and/or SOCKS5BytestreamManager and/or InBandBytestreamManager
176        * to SIProfileFT's constructor, these objects will @b not be deleted on desctruction of SIProfileFT.
177        */
178       SIProfileFT( ClientBase* parent, SIProfileFTHandler* sipfth, SIManager* manager = 0,
179                    SOCKS5BytestreamManager* s5Manager = 0 );
180 
181       /**
182        * Virtual destructor.
183        */
184       virtual ~SIProfileFT();
185 
186       /**
187        * Starts negotiating a file transfer with a remote entity.
188        * @param to The entity to send the file to. Must be a full JID.
189        * @param name The file's name. Mandatory and must not be empty.
190        * @param size The file's size. Mandatory and must be > 0.
191        * @param hash The file content's MD5 hash.
192        * @param desc A description.
193        * @param date The file's last modification date/time. See @xep{0082} for details.
194        * @param mimetype The file's mime-type. Defaults to 'binary/octet-stream' if empty.
195        * @param streamTypes ORed StreamType that can be used for this transfer.
196        * @param from An optional 'from' address to stamp outgoing requests with.
197        * Used in component scenario only. Defaults to empty JID.
198        * @param sid Optionally specify a stream ID (SID). If empty, one will be generated.
199        * @return The requested stream's ID (SID). Empty if conditions above (file name, size)
200        * are not met.
201        */
202       const std::string requestFT( const JID& to, const std::string& name, long size,
203                                    const std::string& hash = EmptyString,
204                                    const std::string& desc = EmptyString,
205                                    const std::string& date = EmptyString,
206                                    const std::string& mimetype = EmptyString,
207                                    int streamTypes = FTTypeAll,
208                                    const JID& from = JID(),
209                                    const std::string& sid = EmptyString );
210 
211       /**
212        * Call this function to accept a file transfer request previously announced by means of
213        * @link gloox::SIProfileFTHandler::handleFTRequest() SIProfileFTHandler::handleFTRequest() @endlink.
214        * @param to The requestor.
215        * @param sid The request's sid, as passed to SIProfileHandler::handleFTRequest().
216        * @param type The desired stream type to use for this file transfer. Defaults to
217        * SOCKS5 Bytestream. You should not use @c FTTypeAll here.
218        * @param from An optional 'from' address to stamp outgoing stanzas with.
219        * Used in component scenario only. Defaults to empty JID.
220        */
221       void acceptFT( const JID& to, const std::string& sid,
222                      StreamType type = FTTypeS5B, const JID& from = JID() );
223 
224       /**
225        * Call this function to decline a FT request previously announced by means of
226        * @link gloox::SIProfileFTHandler::handleFTRequest() SIProfileFTHandler::handleFTRequest() @endlink.
227        * @param to The requestor.
228        * @param sid The request's sid, as passed to SIProfileFTHandler::handleFTRequest().
229        * @param reason The reason for the reject.
230        * @param text An optional human-readable text explaining the decline.
231        */
232       void declineFT( const JID& to, const std::string& sid, SIManager::SIError reason,
233                       const std::string& text = EmptyString );
234 
235       /**
236        * Cancels the given bytestream. Most useful for SOCKS5 bytestreams where no proxies could be found.
237        * The given Bytestream will be deleted.
238        * @param bs The Bytestream to cancel.
239        * @note Can also be used with IBB.
240        */
241       void cancel( Bytestream* bs );
242 
243       /**
244        * To get rid of a bytestream (i.e., close and delete it), call this function.
245        * The remote entity will be notified about the closing of the stream.
246        * @param bs The bytestream to dispose. It will be deleted here.
247        */
248       void dispose( Bytestream* bs );
249 
250       /**
251        * Registers a handler that will be informed about incoming file transfer
252        * requests, i.e. when a remote entity wishes to send a file.
253        * @param sipfth A SIProfileFTHandler to register. Only one handler can be registered
254        * at any one time.
255        */
registerSIProfileFTHandler(SIProfileFTHandler * sipfth)256       void registerSIProfileFTHandler( SIProfileFTHandler* sipfth ) { m_handler = sipfth; }
257 
258       /**
259        * Removes the previously registered file transfer request handler.
260        */
removeSIProfileFTHandler()261       void removeSIProfileFTHandler() { m_handler = 0; }
262 
263       /**
264        * Sets a list of StreamHosts that will be used for subsequent SOCKS5 bytestream requests.
265        * @note At least one StreamHost is required.
266        * @param hosts A list of StreamHosts.
267        */
268       void setStreamHosts( StreamHostList hosts );
269 
270       /**
271        * Adds one StreamHost to the list of SOCKS5 StreamHosts.
272        * @param jid The StreamHost's JID.
273        * @param host The StreamHost's hostname.
274        * @param port The StreamHost's port.
275        */
276       void addStreamHost( const JID& jid, const std::string& host, int port );
277 
278       /**
279        * Tells the interal SOCKS5BytestreamManager which SOCKS5BytestreamServer handles
280        * peer-2-peer SOCKS5 bytestreams.
281        * @param server The SOCKS5BytestreamServer to use.
282        */
registerSOCKS5BytestreamServer(SOCKS5BytestreamServer * server)283       void registerSOCKS5BytestreamServer( SOCKS5BytestreamServer* server )
284         { if( m_socks5Manager ) m_socks5Manager->registerSOCKS5BytestreamServer( server ); }
285 
286       /**
287        * Un-registers any local SOCKS5BytestreamServer.
288        */
removeSOCKS5BytestreamServer()289       void removeSOCKS5BytestreamServer()
290         { if( m_socks5Manager ) m_socks5Manager->removeSOCKS5BytestreamServer(); }
291 
292       // reimplemented from SIProfileHandler
293       virtual void handleSIRequest( const JID& from, const JID& to, const std::string& id,
294                                     const SIManager::SI& si );
295 
296       // reimplemented from SIHandler
297       virtual void handleSIRequestResult( const JID& from, const JID& to, const std::string& sid,
298                                           const SIManager::SI& si );
299 
300       // reimplemented from SIHandler
301       virtual void handleSIRequestError( const IQ& iq, const std::string& sid );
302 
303       // reimplemented from BytestreamHandler
304       virtual void handleIncomingBytestreamRequest( const std::string& sid, const JID& from );
305 
306       // reimplemented from BytestreamHandler
307       virtual void handleIncomingBytestream( Bytestream* bs );
308 
309       // reimplemented from BytestreamHandler
310       virtual void handleOutgoingBytestream( Bytestream* bs );
311 
312       // reimplemented from BytestreamHandler
313       virtual void handleBytestreamError( const IQ& iq, const std::string& sid );
314 
315       // reimplemented from IqHandler.
handleIq(const IQ & iq)316       virtual bool handleIq( const IQ& iq ) { (void)iq; return false; }
317 
318       // reimplemented from IqHandler.
319       virtual void handleIqID( const IQ& iq, int context );
320 
321     private:
322 
323       enum TrackEnum
324       {
325         OOBSent
326       };
327 
328       ClientBase* m_parent;
329       SIManager* m_manager;
330       SIProfileFTHandler* m_handler;
331       SOCKS5BytestreamManager* m_socks5Manager;
332       StreamHostList m_hosts;
333       StringMap m_id2sid;
334       bool m_delManager;
335       bool m_delS5Manager;
336 
337   };
338 
339 }
340 
341 #endif // SIPROFILEFT_H__
342