/// <summary>
        /// Performs a mediated transfer, meaning we send the data over a proxy server.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <param name="proxies"></param>
        /// <exception cref="Socks5Exception">The SOCKS5 connection to the designated
        /// proxy server could not be established.</exception>
        /// <exception cref="XmppErrorException">The server returned an XMPP error code.
        /// Use the Error property of the XmppErrorException to obtain the specific
        /// error condition.</exception>
        /// <exception cref="XmppException">The server returned invalid data or another
        /// unspecified XMPP error occurred.</exception>
        void MediatedTransfer(SISession session, IEnumerable <Streamhost> proxies)
        {
            var proxy = NegotiateProxy(session, proxies);

            // Connect to the designated proxy.
            using (var client = new Socks5Client(proxy.Host, proxy.Port)) {
                // Send the SOCKS5 Connect command.
                string     hostname = Sha1(session.Sid + session.From + session.To);
                SocksReply reply    = client.Request(SocksCommand.Connect, hostname, 0);
                if (reply.Status != ReplyStatus.Succeeded)
                {
                    throw new Socks5Exception("SOCKS5 Connect request failed.");
                }
                // Activate the bytetream.
                var xml = Xml.Element("query", "http://jabber.org/protocol/bytestreams")
                          .Attr("sid", session.Sid).Child(
                    Xml.Element("activate").Text(session.To.ToString()));
                Iq iq = im.IqRequest(IqType.Set, proxy.Jid, im.Jid, xml);
                if (iq.Type == IqType.Error)
                {
                    throw Util.ExceptionFromError(iq, "Could not activate the bytestream.");
                }
                // Finally, go ahead and send the data to the proxy.
                SendData(session, client.GetStream());
            }
        }
        /// <summary>
        /// Processes an IBB 'data' request.
        /// </summary>
        /// <param name="sessionId">The mandatory session id attribute of the 'data'
        /// element.</param>
        /// <param name="stanza">The IQ stanza containing the request.</param>
        /// <exception cref="ArgumentNullException">The sessionId parameter or the
        /// stanza parameter is null.</exception>
        /// <exception cref="ArgumentException">The IQ stanza is missing the
        /// mandatory 'data' element, or the specified session id is not associated
        /// with a file-stream.</exception>
        /// <exception cref="FormatException">The data contained in the 'data' element
        /// is not a valid BASE64-encoded string.</exception>
        /// <exception cref="IOException">The data could not be written to the
        /// file-stream. Consult the InnerException property of the IOException object
        /// to obtain the specific reason.</exception>
        void Data(string sessionId, Iq stanza)
        {
            sessionId.ThrowIfNull("sessionId");
            stanza.ThrowIfNull("stanza");
            var data = stanza.Data["data"];

            if (data == null)
            {
                throw new ArgumentException("Invalid stanza, missing data element.");
            }
            SISession session = siFileTransfer.GetSession(sessionId, stanza.From, im.Jid);

            if (session == null)
            {
                throw new ArgumentException("Invalid session-id.");
            }
            string base64 = data.InnerText;

            // Decode base64 string and write decoded binary data to file.
            byte[] bytes = Convert.FromBase64String(base64);
            try {
                session.Stream.Write(bytes, 0, bytes.Length);
            } catch (Exception e) {
                throw new IOException("The stream could not be written.", e);
            }
            // Update the byte count and raise the 'BytesTransferred' event.
            session.Count = session.Count + bytes.Length;
            BytesTransferred.Raise(this, new BytesTransferredEventArgs(session));
        }
        /// <summary>
        /// Waits for a client connection to be made and subsequently verifies it.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <param name="server">The instance of the SOCKS5 server to accept
        /// client connections on.</param>
        /// <param name="timeout">The number of milliseconds to wait for a client
        /// connection before returning to the caller, or -1 to wait
        /// indefinitely.</param>
        /// <exception cref="SocketException">An error occurred when accessing the
        /// underlying socket of the SOCKS5 server instance.</exception>
        /// <exception cref="Socks5Exception">The SOCKS5 negotiation with the client
        /// failed.</exception>
        /// <exception cref="TimeoutException">A timeout was specified and it
        /// expired.</exception>
        /// <exception cref="IOException">The stream could not be read, or the
        /// operation timed out.</exception>
        void AcceptClientConnection(SISession session, Socks5Server server,
                                    int timeout = -1)
        {
            var request = server.Accept(timeout);

            if (request.Command != SocksCommand.Connect)
            {
                throw new Socks5Exception("Unexpected SOCKS5 command: " +
                                          request.Command);
            }
            if (request.ATyp != ATyp.Domain)
            {
                throw new Socks5Exception("Unexpected ATyp: " + request.ATyp);
            }
            string hash = (string)request.Destination;
            // Calculate the SHA-1 hash and compare it with the one in the request.
            string calculated = Sha1(session.Sid + im.Jid + session.To);

            if (calculated != hash)
            {
                throw new Socks5Exception("Hostname hash mismatch.");
            }
            // We're good to go.
            server.Reply(ReplyStatus.Succeeded, hash, request.Port);
        }
Example #4
0
        /// <summary>
        /// Performs the actual data-transfer implied by the specified
        /// SI session.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <exception cref="ArgumentNullException">The session parameter is
        /// null.</exception>
        /// <exception cref="NotSupportedException">The XMPP extension
        /// implementing this method is not supported by the intended recipient's
        /// XMPP client.</exception>
        /// <exception cref="XmppErrorException">The server or the XMPP entity
        /// with the specified JID returned an XMPP error code. Use the Error
        /// property of the XmppErrorException to obtain the specific error
        /// condition.</exception>
        /// <exception cref="XmppException">The server returned invalid data or
        /// another unspecified XMPP error occurred.</exception>
        public async Task Transfer(SISession session)
        {
            session.ThrowIfNull("session");
            // Open the negotiated IBB.
            await OpenStream(session.To, session.Sid);

            byte[] buf = new byte[blockSize];
            // 'seq' is defined as 16-bit unsigned short value that wraps around.
            ushort seq  = 0;
            long   left = session.Size;

            try
            {
                while (left > 0)
                {
                    int read = session.Stream.Read(buf, 0, blockSize);
                    left = left - read;
                    if (read <= 0)
                    {
                        break;
                    }
                    string b64  = Convert.ToBase64String(buf, 0, read);
                    var    data = Xml.Element("data", "http://jabber.org/protocol/ibb")
                                  .Attr("sid", session.Sid)
                                  .Attr("seq", seq.ToString())
                                  .Text(b64);
                    seq++;
                    Iq response = await im.IqRequest(IqType.Set, session.To, im.Jid, data);

                    if (response.Type == IqType.Error)
                    {
                        throw Util.ExceptionFromError(response);
                    }
                    session.Count = session.Count + read;
                    // Raise the 'BytesTransferred' event.
                    BytesTransferred.Raise(this, new BytesTransferredEventArgs(session));
                }
            }
            catch (ObjectDisposedException)
            {
                // This means the IO-stream has been disposed because we cancelled
                // the transfer. Just fall through.
            }
            catch
            {
                // The IQ response is of type 'error', the other site has cancelled
                // the transfer.
                TransferAborted.Raise(this, new TransferAbortedEventArgs(session));
                // Rethrow.
                throw;
            }
            finally
            {
                // Gracefully close the IBB.
                await CloseStream(session.To, session.Sid);
            }
        }
Example #5
0
        /// <summary>
        /// Performs the actual data-transfer implied by the specified
        /// SI session.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <exception cref="ArgumentNullException">The session parameter is
        /// null.</exception>
        /// <exception cref="NotSupportedException">The XMPP extension
        /// implementing this method is not supported by the intended recipient's
        /// XMPP client.</exception>
        /// <exception cref="XmppErrorException">The server or the XMPP entity
        /// with the specified JID returned an XMPP error code. Use the Error
        /// property of the XmppErrorException to obtain the specific error
        /// condition.</exception>
        /// <exception cref="XmppException">The server returned invalid data or
        /// another unspecified XMPP error occurred.</exception>
        public void Transfer(SISession session)
        {
            IEnumerable <Streamhost> proxies = null;

            // Determine if this is going to be a direct or a mediated transfer.
            if (ProxyAllowed)
            {
                try
                {
                    var externalAddresses = GetExternalAddresses();
                    // If all of our external addresses are behind NATs, we may need a proxy.
                    bool behindNAT = externalAddresses.All(addr => BehindNAT(addr));
                    if (behindNAT)
                    {
                        // Look for user-defined proxies first.
                        if (Proxies.Count > 0)
                        {
                            proxies = Proxies;
                        }
                        else
                        {
                            // Otherwise query XMPP server for a list of proxies.
                            proxies = GetProxyList();
                        }
                    }
                }
                catch
                {
                    // Retrieving the external addresses may fail as well as querying
                    // the XMPP server for proxies. In these cases, we at least try
                    // with a direct transfer.
                }
            }

            try
            {
                if (proxies != null && proxies.Count() > 0)
                {
                    MediatedTransfer(session, proxies);
                }
                else
                {
                    DirectTransfer(session);
                }
            }
            catch (Exception)
            {
                // Raise the 'TransferAborted' event.
                TransferAborted.Raise(this, new TransferAbortedEventArgs(session));
                // Invalidate the session.
                siFileTransfer.InvalidateSession(session.Sid);
            }
        }
Example #6
0
        /// <summary>
        /// Cancels the specified file-transfer.
        /// </summary>
        /// <param name="transfer">The file-transfer to cancel.</param>
        /// <exception cref="ArgumentNullException">The transfer parameter is
        /// null.</exception>
        /// <exception cref="ArgumentException">The specified transfer instance does
        /// not represent an active data-transfer operation.</exception>
        public void CancelFileTransfer(FileTransfer transfer)
        {
            transfer.ThrowIfNull("transfer");
            SISession session = GetSession(transfer.SessionId, transfer.From,
                                           transfer.To);

            if (session == null)
            {
                throw new ArgumentException("The specified transfer instance does not " +
                                            "represent an active data-transfer operation.");
            }
            session.Extension.CancelTransfer(session);
        }
Example #7
0
        /// <summary>
        /// Cancels the specified file-transfer.
        /// </summary>
        /// <param name="from">From Jid</param>
        /// <param name="sid">Sid</param>
        /// <param name="to">To Jid</param>
        /// <exception cref="ArgumentNullException">The transfer parameter is
        /// null.</exception>
        /// <exception cref="ArgumentException">The specified transfer instance does
        /// not represent an active data-transfer operation.</exception>
        public void CancelFileTransfer(string sid, Jid from, Jid to)
        {
            sid.ThrowIfNullOrEmpty("sid");
            from.ThrowIfNull("from");
            to.ThrowIfNull("to");
            SISession session = GetSession(sid, from,
                                           to);

            if (session == null)
            {
                throw new ArgumentException(String.Format("The specified transfer instance does not " +
                                                          "represent an active data-transfer operation.:sid {0}, file {1}, from {2}, to {3}", session.Sid, session.Stream.ToString(), session.From, session.To));
            }

            session.Extension.CancelTransfer(session);
        }
        /// <summary>
        /// Negotiates with the target which of the specified SOCKS5 proxies to use.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <param name="proxies">An enumerable collection of SOCKS5 proxy servers
        /// to advertise to the target.</param>
        /// <returns>The proxy server to use for the data transfer.</returns>
        /// <exception cref="XmppErrorException">The server returned an XMPP error code.
        /// Use the Error property of the XmppErrorException to obtain the specific
        /// error condition.</exception>
        /// <exception cref="XmppException">The server returned invalid data or another
        /// unspecified XMPP error occurred.</exception>
        Streamhost NegotiateProxy(SISession session, IEnumerable <Streamhost> proxies)
        {
            // Compile XML containing our list of proxy servers.
            var xml = Xml.Element("query", "http://jabber.org/protocol/bytestreams")
                      .Attr("sid", session.Sid);

            foreach (var proxy in proxies)
            {
                xml.Child(Xml.Element("streamhost")
                          .Attr("jid", proxy.Jid.ToString())
                          .Attr("host", proxy.Host)
                          .Attr("port", proxy.Port.ToString()));
            }
            // Wait for the other site to tell us which proxy server it selected.
            var iq = im.IqRequest(IqType.Set, session.To, im.Jid, xml);

            if (iq.Type == IqType.Error)
            {
                throw Util.ExceptionFromError(iq, "The SOCKS5 negotiation failed.");
            }
            var query = iq.Data["query"];

            if (query == null || query.NamespaceURI !=
                "http://jabber.org/protocol/bytestreams")
            {
                throw new XmppException("Erroneous response.");
            }
            if (query.GetAttribute("sid") != session.Sid)
            {
                throw new XmppException("Invalid session identifier.");
            }
            var used = query["streamhost-used"];

            if (used == null)
            {
                throw new XmppException("Missing streamhost-used element.");
            }
            var proxyJid   = used.GetAttribute("jid");
            var streamhost = proxies.FirstOrDefault(proxy => proxy.Jid == proxyJid);

            if (streamhost == null)
            {
                throw new XmppException("Invalid streamhost JID.");
            }
            return(streamhost);
        }
        /// <summary>
        /// Receives the actual data after SOCKS5 negotiation has been completed.
        /// </summary>
        /// <param name="stanza">The original requesting IQ stanza.</param>
        /// <param name="sid">The session-id associated with the
        /// data-transfer.</param>
        /// <param name="stream">The stream from which to read the incoming
        /// data.</param>
        /// <exception cref="XmppException">The specifid SID does not denote a
        /// valid SI session.</exception>
        private async Task ReceiveData(Iq stanza, string sid, Stream stream)
        {
            SISession session = siFileTransfer.GetSession(sid, stanza.From, stanza.To);

            if (session == null)
            {
                throw new XmppException("Invalid session-id: " + sid);
            }
            long left = session.Size;

            try
            {
                while (left > 0)
                {
                    byte[] buffer = new byte[4096];
                    int    read   = await stream.ReadAsync(buffer, 0, buffer.Length);

                    if (read <= 0)
                    {
                        break;
                    }
                    left = left - read;
                    await session.Stream.WriteAsync(buffer, 0, read);

                    // Update the byte count and raise the 'BytesTransferred' event.
                    session.Count = session.Count + read;
                    BytesTransferred.Raise(this, new BytesTransferredEventArgs(session));
                }
            }
            catch (ObjectDisposedException)
            {
                // This means the IO-stream has been disposed because we cancelled
                // the transfer. Just fall through.
            }
            finally
            {
                // Tear down the SI session.
                siFileTransfer.InvalidateSession(sid);
                // If not all bytes have been transferred, the data-transfer must have
                // been aborted prematurely.
                if (session.Count < session.Size)
                {
                    TransferAborted.Raise(this, new TransferAbortedEventArgs(session));
                }
            }
        }
Example #10
0
        /// <summary>
        /// Cancels the specified file-transfer.
        /// </summary>
        /// <param name="from">From Jid</param>
        /// <param name="sid">Sid</param>
        /// <param name="to">To Jid</param>
        /// <exception cref="ArgumentNullException">The transfer parameter is
        /// null.</exception>
        /// <exception cref="ArgumentException">The specified transfer instance does
        /// not represent an active data-transfer operation.</exception>
        public void CancelFileTransfer(string sid, Jid from, Jid to)
        {
            sid.ThrowIfNullOrEmpty("sid");
            from.ThrowIfNull("from");
            to.ThrowIfNull("to");
            SISession session = GetSession(sid, from,
                                           to);

            if (session == null)
            {
                throw new ArgumentException("The specified transfer instance does not " +
                                            "represent an active data-transfer operation.");
            }
            //#if DEBUG
            //System.Diagnostics.Debug.WriteLine("SI File Transfer CancelFileTransfer, sid {0}, file {1}, from {2}, to {3}", session.Sid, session.Stream.ToString(), session.From, session.To);
            //#endif

            session.Extension.CancelTransfer(session);
        }
Example #11
0
        /// <summary>
        /// Invoked once the result of a pending stream-initiation operation has been
        /// received.
        /// </summary>
        /// <param name="result">The result of the stream-initiation operation. If
        /// this parameter is null, stream-initiation failed.</param>
        /// <param name="to">The JID of the XMPP user to offer the file to.</param>
        /// <param name="stream">The stream to read the file-data from.</param>
        /// <param name="name">The name of the file, as offered to the XMPP user
        /// with the specified JID.</param>
        /// <param name="size">The number of bytes to transfer.</param>
        /// <param name="cb">A callback method invoked once the other site has
        /// accepted or rejected the file-transfer request.</param>
        /// <param name="description">A description of the file so the receiver can
        /// better understand what is being sent.</param>
        /// <remarks>This is called in the context of an arbitrary thread.</remarks>
        private void OnInitiationResult(InitiationResult result, Jid to, string name,
                                        Stream stream, long size, string description, Action <bool, FileTransfer> cb)
        {
            FileTransfer transfer = new FileTransfer(im.Jid, to, name, size, null,
                                                     description);

            try
            {
                // Get the instance of the data-stream extension that the other site has
                // selected.
                IDataStream ext = im.GetExtension(result.Method) as IDataStream;
                // Register the session.
                SISession session = new SISession(result.SessionId, stream, size, false,
                                                  im.Jid, to, ext);
                siSessions.TryAdd(result.SessionId, session);
                // Store the file's meta data.
                metaData.TryAdd(result.SessionId, new FileMetaData(name, description));
                // Invoke user-provided callback.
                if (cb != null)
                {
                    cb.Invoke(true, transfer);
                }
                // Perform the actual data-transfer.
                try
                {
                    ext.Transfer(session);
                }
                catch (Exception e)
                {
                    System.Diagnostics.Debug.WriteLine(e.Message + e.StackTrace + e.ToString());
                    // Nothing to do here.
                }
            }
            catch
            {
                // Something went wrong. Invoke user-provided callback to let them know
                // the file-transfer can't be performed.
                if (cb != null)
                {
                    cb.Invoke(false, transfer);
                }
            }
        }
Example #12
0
        /// <summary>
        /// Processes an IBB 'close' request.
        /// </summary>
        /// <param name="sessionId">The mandatory session id attribute of the 'close'
        /// element.</param>
        /// <param name="stanza">The IQ stanza containing the request.</param>
        /// <exception cref="ArgumentNullException">The sessionId parameter or the
        /// stanza parameter is null.</exception>
        void Close(string sessionId, Iq stanza)
        {
            sessionId.ThrowIfNull("sessionId");
            stanza.ThrowIfNull("stanza");
            SISession session = siFileTransfer.GetSession(sessionId,
                                                          stanza.From, stanza.To);

            // We don't allow the other site to close a session that we opened.
            if (session != null)
            {
                siFileTransfer.InvalidateSession(sessionId);
                // Had all bytes been received when we got the 'close' request?
                // Otherwise, the other site cancelled the transfer prematurely.
                if (session.Count < session.Size)
                {
                    TransferAborted.Raise(this, new TransferAbortedEventArgs(session));
                }
            }
        }
Example #13
0
        /// <summary>
        /// Sends the actual data associated with the specified session over the
        /// specified stream.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <param name="stream">The (network) stream representing the connection
        /// to the client.</param>
        private void SendData(SISession session, Stream stream)
        {
            long left = session.Size;

            try
            {
                while (left > 0)
                {
                    byte[] buffer = new byte[4096];
                    int    read   = session.Stream.Read(buffer, 0,
                                                        (int)Math.Min(left, buffer.Length));
                    if (read > 0)
                    {
                        stream.Write(buffer, 0, read);
                    }
                    else
                    {
                        break;
                    }
                    left = left - read;
                    // Update the byte count and raise the 'BytesTransferred' event.
                    session.Count = session.Count + read;
                    BytesTransferred.Raise(this, new BytesTransferredEventArgs(session));
                }
            }
            catch (ObjectDisposedException)
            {
                // This means the IO-stream has been disposed because we cancelled
                // the transfer. Just fall through.
            }
            finally
            {
                // Tear down the SI session.
                siFileTransfer.InvalidateSession(session.Sid);
                // If not all bytes have been transferred, the data-transfer must have
                // been aborted prematurely.
                if (session.Count < session.Size)
                {
                    TransferAborted.Raise(this, new TransferAbortedEventArgs(session));
                }
            }
        }
Example #14
0
 /// <summary>
 /// Invoked whenever a 'Stream Initiation' request for file transfers
 /// is received.
 /// </summary>
 /// <param name="from">The JID of the XMPP entity that wishes to initiate
 /// the data-stream.</param>
 /// <param name="si">The 'si' element of the request.</param>
 /// <returns>The response to the SI request or an error element to include
 /// in the IQ response.</returns>
 private XmlElement OnStreamInitiationRequest(Jid from, XmlElement si)
 {
     try
     {
         string method = SelectStreamMethod(si["feature"]);
         // If the session-id is already in use, we cannot process the request.
         string sid = si.GetAttribute("id");
         if (String.IsNullOrEmpty(sid) || siSessions.ContainsKey(sid))
         {
             return(new XmppError(ErrorType.Cancel, ErrorCondition.Conflict).Data);
         }
         // Extract file information and hand to user.
         var    file           = si["file"];
         string desc           = file["desc"] != null ? file["desc"].InnerText : null,
                name           = file.GetAttribute("name");
         int          size     = int.Parse(file.GetAttribute("size"));
         FileTransfer transfer = new FileTransfer(from, im.Jid, name, size,
                                                  sid, desc);
         string savePath = TransferRequest.Invoke(transfer);
         // User has rejected the request.
         if (savePath == null)
         {
             return(new XmppError(ErrorType.Cancel, ErrorCondition.NotAcceptable).Data);
         }
         // Create an SI session instance.
         SISession session = new SISession(sid, File.OpenWrite(savePath),
                                           size, true, from, im.Jid, im.GetExtension(method) as IDataStream);
         siSessions.TryAdd(sid, session);
         // Store the file's meta data.
         metaData.TryAdd(sid, new FileMetaData(name, desc));
         // Construct and return the negotiation result.
         return(Xml.Element("si", "http://jabber.org/protocol/si").Child(
                    FeatureNegotiation.Create(new SubmitForm(
                                                  new ListField("stream-method", method)))));
     }
     catch (Exception e)
     {
         System.Diagnostics.Debug.WriteLine("Exception raised", e.ToString() + e.StackTrace);
         return(new XmppError(ErrorType.Cancel, ErrorCondition.BadRequest).Data);
     }
 }
Example #15
0
 /// <summary>
 /// Initializes a new instance of the BytesTransferredEventArgs class.
 /// </summary>
 /// <param name="session">The session for which the event is raised.</param>
 /// <exception cref="ArgumentNullException">The session parameter
 /// is null.</exception>
 public TransferAbortedEventArgs(SISession session)
 {
     session.ThrowIfNull("session");
     Session = session;
 }
        /// <summary>
        /// Performs a direct transfer, meaning we act as a SOCKS5 server.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <exception cref="Socks5Exception">The SOCKS5 server could not be
        /// instantiated.</exception>
        /// <exception cref="XmppErrorException">The server returned an XMPP error code.
        /// Use the Error property of the XmppErrorException to obtain the specific
        /// error condition.</exception>
        /// <exception cref="XmppException">The server returned invalid data or another
        /// unspecified XMPP error occurred.</exception>
        private async Task DirectTransfer(SISession session)
        {
            // Create the listening SOCKS5 server.
            Socks5Server socks5Server = null;

            try
            {
                socks5Server = CreateSocks5Server(serverPortFrom, serverPortTo);
            }
            catch (Exception e)
            {
                throw new Socks5Exception("The SOCKS5 server could not be created.", e);
            }
            IEnumerable <IPAddress> externalAddresses = null;

            try
            {
                externalAddresses = await GetExternalAddresses();

                // Check if we might need to forward the server port.
            }
            catch (NotSupportedException)
            {
                // Not much we can do.
            }
            // Waiting for a client connection is a blocking call and we need to
            // negotiate the SOCKS5 connection after we send the IQ request but
            // _before_ we wait for the IQ response.
            Task.Run(async() =>
            {
                try
                {
                    await AcceptClientConnection(session, socks5Server, acceptTimeout);
                    await SendData(session, socks5Server.GetStream());
                }
                finally
                {
                    socks5Server.Close();
                }
            });

            // Send target a list of streamhosts, one for each active network interface.
            var xml = Xml.Element("query", "http://jabber.org/protocol/bytestreams")
                      .Attr("sid", session.Sid);
            // Compile a set of all our IP addresses that we can advertise.
            ISet <IPAddress> ips = new HashSet <IPAddress>();

            if (externalAddresses != null)
            {
                ips.UnionWith(externalAddresses);
            }
            ips.UnionWith(GetIpAddresses());
            foreach (var ip in ips)
            {
                xml.Child(Xml.Element("streamhost")
                          .Attr("jid", im.Jid.ToString())
                          .Attr("host", ip.ToString())
                          .Attr("port", socks5Server.Port.ToString()));
            }
            // Send IQ with streamhosts to the target.
            var iq = await im.IqRequest(IqType.Set, session.To, im.Jid, xml);

            if (iq.Type == IqType.Error)
            {
                throw Util.ExceptionFromError(iq, "The SOCKS5 connection could not " +
                                              "be established.");
            }
        }
		/// <summary>
		/// Initializes a new instance of the BytesTransferredEventArgs class.
		/// </summary>
		/// <param name="session">The session for which the event is raised.</param>
		/// <exception cref="ArgumentNullException">The session parameter
		/// is null.</exception>
		public BytesTransferredEventArgs(SISession session) {
			session.ThrowIfNull("session");
			Session = session;
		}
 /// <summary>
 /// Invoked once the result of a pending stream-initiation operation has been
 /// received.
 /// </summary>
 /// <param name="result">The result of the stream-initiation operation. If
 /// this parameter is null, stream-initiation failed.</param>
 /// <param name="to">The JID of the XMPP user to offer the file to.</param>
 /// <param name="stream">The stream to read the file-data from.</param>
 /// <param name="name">The name of the file, as offered to the XMPP user
 /// with the specified JID.</param>
 /// <param name="size">The number of bytes to transfer.</param>
 /// <param name="cb">A callback method invoked once the other site has
 /// accepted or rejected the file-transfer request.</param>
 /// <param name="description">A description of the file so the receiver can
 /// better understand what is being sent.</param>
 /// <remarks>This is called in the context of an arbitrary thread.</remarks>
 private void OnInitiationResult(InitiationResult result, Jid to, string name,
     Stream stream, long size, string description, Action<bool, FileTransfer> cb)
 {
     FileTransfer transfer = new FileTransfer(im.Jid, to, name, size, null,
         description);
     try
     {
         // Get the instance of the data-stream extension that the other site has
         // selected.
         IDataStream ext = im.GetExtension(result.Method) as IDataStream;
         // Register the session.
         SISession session = new SISession(result.SessionId, stream, size, false,
             im.Jid, to, ext);
         siSessions.TryAdd(result.SessionId, session);
         // Store the file's meta data.
         metaData.TryAdd(result.SessionId, new FileMetaData(name, description));
         // Invoke user-provided callback.
         if (cb != null)
             cb.Invoke(true, transfer);
         // Perform the actual data-transfer.
         try
         {
             ext.Transfer(session);
         }
         catch (Exception e)
         {
             System.Diagnostics.Debug.WriteLine(e.Message + e.StackTrace + e.ToString());
             // Nothing to do here.
         }
     }
     catch
     {
         // Something went wrong. Invoke user-provided callback to let them know
         // the file-transfer can't be performed.
         if (cb != null)
             cb.Invoke(false, transfer);
     }
 }
 /// <summary>
 /// Invoked whenever a 'Stream Initiation' request for file transfers
 /// is received.
 /// </summary>
 /// <param name="from">The JID of the XMPP entity that wishes to initiate
 /// the data-stream.</param>
 /// <param name="si">The 'si' element of the request.</param>
 /// <returns>The response to the SI request or an error element to include
 /// in the IQ response.</returns>
 private XmlElement OnStreamInitiationRequest(Jid from, XmlElement si)
 {
     try
     {
         string method = SelectStreamMethod(si["feature"]);
         // If the session-id is already in use, we cannot process the request.
         string sid = si.GetAttribute("id");
         if (String.IsNullOrEmpty(sid) || siSessions.ContainsKey(sid))
             return new XmppError(ErrorType.Cancel, ErrorCondition.Conflict).Data;
         // Extract file information and hand to user.
         var file = si["file"];
         string desc = file["desc"] != null ? file["desc"].InnerText : null,
             name = file.GetAttribute("name");
         int size = int.Parse(file.GetAttribute("size"));
         FileTransfer transfer = new FileTransfer(from, im.Jid, name, size,
             sid, desc);
         string savePath = TransferRequest.Invoke(transfer);
         // User has rejected the request.
         if (savePath == null)
             return new XmppError(ErrorType.Cancel, ErrorCondition.NotAcceptable).Data;
         // Create an SI session instance.
         SISession session = new SISession(sid, File.OpenWrite(savePath),
             size, true, from, im.Jid, im.GetExtension(method) as IDataStream);
         siSessions.TryAdd(sid, session);
         // Store the file's meta data.
         metaData.TryAdd(sid, new FileMetaData(name, desc));
         // Construct and return the negotiation result.
         return Xml.Element("si", "http://jabber.org/protocol/si").Child(
             FeatureNegotiation.Create(new SubmitForm(
                 new ListField("stream-method", method))));
     }
     catch (Exception e)
     {
         System.Diagnostics.Debug.WriteLine("Exception raised", e.ToString() + e.StackTrace);
         return new XmppError(ErrorType.Cancel, ErrorCondition.BadRequest).Data;
     }
 }
Example #20
0
 /// <summary>
 /// Initializes a new instance of the FileTransfer class.
 /// </summary>
 /// <param name="session">The SISession instance to initialize this
 /// instance from.</param>
 /// <param name="name">The name of the file.</param>
 /// <param name="description">A description of the file.</param>
 /// <exception cref="ArgumentNullException">The session parameter or
 /// the name parameter is null.</exception>
 internal FileTransfer(SISession session, string name, string description) :
     this(session.From, session.To, name, session.Size, session.Sid,
          description, session.Count)
 {
 }
Example #21
0
 /// <summary>
 /// Initializes a new instance of the FileTransfer class.
 /// </summary>
 /// <param name="session">The SISession instance to initialize this
 /// instance from.</param>
 /// <param name="name">The name of the file.</param>
 /// <param name="description">A description of the file.</param>
 /// <exception cref="ArgumentNullException">The session parameter or
 /// the name parameter is null.</exception>
 internal FileTransfer(SISession session, string name, string description) :
     this(session.From, session.To, name, session.Size, session.Sid,
     description, session.Count)
 {
 }
Example #22
0
        /// <summary>
        /// Performs a direct transfer, meaning we act as a SOCKS5 server.
        /// </summary>
        /// <param name="session">The SI session whose data to transfer.</param>
        /// <exception cref="Socks5Exception">The SOCKS5 server could not be
        /// instantiated.</exception>
        /// <exception cref="XmppErrorException">The server returned an XMPP error code.
        /// Use the Error property of the XmppErrorException to obtain the specific
        /// error condition.</exception>
        /// <exception cref="XmppException">The server returned invalid data or another
        /// unspecified XMPP error occurred.</exception>
        void DirectTransfer(SISession session)
        {
            // Create the listening SOCKS5 server.
            Socks5Server socks5Server = null;

            try {
                socks5Server = CreateSocks5Server(serverPortFrom, serverPortTo);
            } catch (Exception e) {
                throw new Socks5Exception("The SOCKS5 server could not be created.", e);
            }
            IEnumerable <IPAddress> externalAddresses = null;

            try {
                externalAddresses = GetExternalAddresses();
                // Check if we might need to forward the server port.
#if WINDOWSPLATFORM
                if (externalAddresses.Any(addr => BehindNAT(addr)) && UseUPnP)
                {
                    try {
                        UPnP.ForwardPort(socks5Server.Port, ProtocolType.Tcp,
                                         "XMPP SOCKS5 File-transfer");
                    } catch (InvalidOperationException) {
                        // If automatic port forwarding failed for whatever reason, just
                        // go on normally. The user can still configure forwarding manually.
                    }
                }
#endif
            } catch (NotSupportedException) {
                // Not much we can do.
            }
            // Waiting for a client connection is a blocking call and we need to
            // negotiate the SOCKS5 connection after we send the IQ request but
            // _before_ we wait for the IQ response.
            Task.Factory.StartNew(() => {
                try {
                    AcceptClientConnection(session, socks5Server, acceptTimeout);
                    SendData(session, socks5Server.GetStream());
                } finally {
                    socks5Server.Close();
                }
            });

            // Send target a list of streamhosts, one for each active network interface.
            var xml = Xml.Element("query", "http://jabber.org/protocol/bytestreams")
                      .Attr("sid", session.Sid);
            // Compile a set of all our IP addresses that we can advertise.
            ISet <IPAddress> ips = new HashSet <IPAddress>();
            if (externalAddresses != null)
            {
                ips.UnionWith(externalAddresses);
            }
            ips.UnionWith(GetIpAddresses());
            foreach (var ip in ips)
            {
                xml.Child(Xml.Element("streamhost")
                          .Attr("jid", im.Jid.ToString())
                          .Attr("host", ip.ToString())
                          .Attr("port", socks5Server.Port.ToString()));
            }
            // Send IQ with streamhosts to the target.
            var iq = im.IqRequest(IqType.Set, session.To, im.Jid, xml);
            if (iq.Type == IqType.Error)
            {
                throw Util.ExceptionFromError(iq, "The SOCKS5 connection could not " +
                                              "be established.");
            }
        }
Example #23
0
 /// <summary>
 /// Cancels the data-transfer implied by the specified SI session.
 /// </summary>
 /// <param name="session">The SI session whose data-transfer to
 /// cancel.</param>
 /// <exception cref="ArgumentNullException">The session parameter is
 /// null.</exception>
 public void CancelTransfer(SISession session)
 {
     session.ThrowIfNull("session");
     siFileTransfer.InvalidateSession(session.Sid);
 }
		/// <summary>
		/// Initializes a new instance of the BytesTransferredEventArgs class.
		/// </summary>
		/// <param name="session">The session for which the event is raised.</param>
		/// <exception cref="ArgumentNullException">The session parameter
		/// is null.</exception>
		public TransferAbortedEventArgs(SISession session) {
			session.ThrowIfNull("session");
			Session = session;
		}
 /// <summary>
 /// Initializes a new instance of the BytesTransferredEventArgs class.
 /// </summary>
 /// <param name="session">The session for which the event is raised.</param>
 /// <exception cref="ArgumentNullException">The session parameter
 /// is null.</exception>
 public BytesTransferredEventArgs(SISession session)
 {
     session.ThrowIfNull("session");
     Session = session;
 }
Example #26
0
 /// <summary>
 /// Cancels the data-transfer implied by the specified SI session.
 /// </summary>
 /// <param name="session">The SI session whose data-transfer to
 /// cancel.</param>
 /// <exception cref="ArgumentNullException">The session parameter is
 /// null.</exception>
 public void CancelTransfer(SISession session)
 {
     session.ThrowIfNull("session");
     siFileTransfer.InvalidateSession(session.Sid);
     TransferAborted.Raise(this, new TransferAbortedEventArgs(session));
 }