Esempio n. 1
0
        partial void ProcessEachInitIvExpand2(InitIvExpand2 initIvExpand2)
        {
            packetHandler.ReceivedFinalInitAck();

            var(publicKey, privateKey) = TsCrypt.GenerateTemporaryKey();

            var ekBase64 = Convert.ToBase64String(publicKey);
            var toSign   = new byte[86];

            Array.Copy(publicKey, 0, toSign, 0, 32);
            var beta = Convert.FromBase64String(initIvExpand2.Beta);

            Array.Copy(beta, 0, toSign, 32, 54);
            var sign  = TsCrypt.Sign(connectionDataFull.Identity.PrivateKey, toSign);
            var proof = Convert.ToBase64String(sign);

            ClientEk(ekBase64, proof);

            var result = tsCrypt.CryptoInit2(initIvExpand2.License, initIvExpand2.Omega, initIvExpand2.Proof, initIvExpand2.Beta, privateKey);

            if (!result)
            {
                DisconnectInternal(context, CommandError.Custom($"Failed to calculate shared secret: {result.Error}"));
                return;
            }

            DefaultClientInit();
        }
Esempio n. 2
0
        async partial void ProcessEachInitIvExpand2(InitIvExpand2 initIvExpand2)
        {
            var ctx = context;

            if (ctx is null)
            {
                throw new InvalidOperationException("context should be set");
            }

            ctx.PacketHandler.ReceivedFinalInitAck();

            var(publicKey, privateKey) = TsCrypt.GenerateTemporaryKey();

            var ekBase64 = Convert.ToBase64String(publicKey);
            var toSign   = new byte[86];

            Array.Copy(publicKey, 0, toSign, 0, 32);
            var beta = Convert.FromBase64String(initIvExpand2.Beta);

            Array.Copy(beta, 0, toSign, 32, 54);
            var sign  = TsCrypt.Sign(ctx.ConnectionDataFull.Identity.PrivateKey, toSign);
            var proof = Convert.ToBase64String(sign);

            await ClientEk(ekBase64, proof);

            var result = ctx.TsCrypt.CryptoInit2(initIvExpand2.License, initIvExpand2.Omega, initIvExpand2.Proof, initIvExpand2.Beta, privateKey);

            if (!result)
            {
                ChangeState(ctx, TsClientStatus.Disconnected, CommandError.Custom($"Failed to calculate shared secret: {result.Error}"));
                return;
            }

            await DefaultClientInit(ctx);
        }
Esempio n. 3
0
        /// <summary>Tries to connect to a server.</summary>
        /// <param name="conData">Set the connection information properties as needed.
        /// For further details about each setting see the respective property documentation in <see cref="ConnectionData"/></param>
        /// <exception cref="ArgumentException">When some required values are not set or invalid.</exception>
        /// <exception cref="TsException">When the connection could not be established.</exception>
        public override async CmdR Connect(ConnectionData conData)
        {
            scheduler.VerifyOwnThread();
            if (!(conData is ConnectionDataFull conDataFull))
            {
                throw new ArgumentException($"Use the {nameof(ConnectionDataFull)} derivative to connect with the full client.", nameof(conData));
            }
            if (conDataFull.Identity is null)
            {
                throw new ArgumentNullException(nameof(conDataFull.Identity));
            }
            if (conDataFull.VersionSign is null)
            {
                throw new ArgumentNullException(nameof(conDataFull.VersionSign));
            }

            await Disconnect();

            remoteAddress = await TsDnsResolver.TryResolve(conData.Address);

            if (remoteAddress is null)
            {
                return(CommandError.Custom("Could not read or resolve address."));
            }

            ConnectionData  = conData;
            ServerConstants = TsConst.Default;
            Book.Reset();
            returnCode = 0;

            var ctx = new ConnectionContext(conDataFull);

            context = ctx;

            ctx.PacketHandler.PacketEvent = (ref Packet <S2C> packet) =>
            {
                if (status == TsClientStatus.Disconnected)
                {
                    return;
                }
                PacketEvent(ctx, ref packet);
            };
            ctx.PacketHandler.StopEvent = (closeReason) =>
            {
                _ = scheduler.Invoke(() =>
                {
                    ctx.ExitReason ??= closeReason;
                    ChangeState(ctx, TsClientStatus.Disconnected);
                });
            };

            ChangeState(ctx, TsClientStatus.Connecting);
            if (!ctx.PacketHandler.Connect(remoteAddress).GetOk(out var error))
            {
                ChangeState(ctx, TsClientStatus.Disconnected);
                return(CommandError.Custom(error));
            }
            return(await ctx.ConnectEvent.Task);            // TODO check error state
        }
Esempio n. 4
0
        // Local event processing

        partial void ProcessEachInitIvExpand(InitIvExpand initIvExpand)
        {
            packetHandler.ReceivedFinalInitAck();

            var result = tsCrypt.CryptoInit(initIvExpand.Alpha, initIvExpand.Beta, initIvExpand.Omega);

            if (!result)
            {
                DisconnectInternal(context, CommandError.Custom($"Failed to calculate shared secret: {result.Error}"));
                return;
            }

            DefaultClientInit();
        }
        /// <summary>Gets information about the current transfer status.</summary>
        /// <param name="token">The transfer to check.</param>
        /// <returns>Returns an information object or <code>null</code> when not available.</returns>
        public async Task <R <FileTransfer, CommandError> > GetStats(FileTransferToken token)
        {
            if (token.Status != TransferStatus.Transfering)
            {
                return(CommandError.Custom("No transfer found"));
            }

            var result = await FileTransferList();

            if (result.Ok)
            {
                return(result.Value.Where(x => x.ServerFileTransferId == token.ServerTransferId).MapToSingle());
            }
            return(result.Error);
        }
Esempio n. 6
0
        public async CmdR UploadAvatar(System.IO.Stream image)
        {
            var token = await UploadFile(image, ChannelId.Null, "/avatar", overwrite : true, createMd5 : true);

            if (!token.Ok)
            {
                return(CommandError.Custom("Avatar upload failed: " + token.Error.ErrorFormat()));
            }
            if (token.Value.Status != TransferStatus.Done)
            {
                return(CommandError.Custom("Avatar upload failed"));
            }
            var md5 = string.Concat(token.Value.Md5Sum.Select(x => x.ToString("x2")));

            return(await SendVoid(new TsCommand("clientupdate") { { "client_flag_avatar", md5 } }));
        }
        /// <summary>Gets information about the current transfer status.</summary>
        /// <param name="token">The transfer to check.</param>
        /// <returns>Returns an information object or <code>null</code> when not available.</returns>
        public R <FileTransfer, CommandError> GetStats(FileTransferToken token)
        {
            lock (token)
            {
                if (token.Status != TransferStatus.Transfering)
                {
                    return(CommandError.Custom("No transfer found"));
                }
            }
            var result = parent.FileTransferList();

            if (result.Ok)
            {
                return(result.Value.Where(x => x.ServerFileTransferId == token.ServerTransferId).WrapSingle());
            }
            return(R <FileTransfer, CommandError> .Err(result.Error));
        }
Esempio n. 8
0
        public override async CmdR Connect(ConnectionData conData)
        {
            remoteAddress = await TsDnsResolver.TryResolve(conData.Address, TsDnsResolver.TsQueryDefaultPort);

            if (remoteAddress is null)
            {
                return(CommandError.Custom("Could not read or resolve address."));
            }

            NetworkStream tcpStream;

            try
            {
                connecting = true;

                await tcpClient.ConnectAsync(remoteAddress.Address, remoteAddress.Port);

                ConnectionData = conData;

                tcpStream = tcpClient.GetStream();
                tcpReader = new StreamReader(tcpStream, Tools.Utf8Encoder);
                tcpWriter = new StreamWriter(tcpStream, Tools.Utf8Encoder)
                {
                    NewLine = "\n"
                };

                if (await tcpReader.ReadLineAsync() != "TS3")
                {
                    return(CommandError.Custom("Protocol violation. The stream must start with 'TS3'"));
                }
                if (string.IsNullOrEmpty(await tcpReader.ReadLineAsync()))
                {
                    await tcpReader.ReadLineAsync();
                }
            }
            catch (SocketException ex) { return(CommandError.Custom("Could not connect: " + ex.Message)); }
            finally { connecting = false; }

            cts = new CancellationTokenSource();
            dispatcher.Init(InvokeEvent, conData.LogId);
            _ = NetworkLoop(tcpStream, cts.Token);
            return(R.Ok);
        }
Esempio n. 9
0
        // Local event processing

        async partial void ProcessEachInitIvExpand(InitIvExpand initIvExpand)
        {
            var ctx = context;

            if (ctx is null)
            {
                throw new InvalidOperationException("context should be set");
            }

            ctx.PacketHandler.ReceivedFinalInitAck();

            var result = ctx.TsCrypt.CryptoInit(initIvExpand.Alpha, initIvExpand.Beta, initIvExpand.Omega);

            if (!result)
            {
                ChangeState(ctx, TsClientStatus.Disconnected, CommandError.Custom($"Failed to calculate shared secret: {result.Error}"));
                return;
            }

            await DefaultClientInit(ctx);
        }
        /// <summary>Resumes a download from a previously stopped position.</summary>
        /// <param name="token">The aborted token.</param>
        public async Task <E <CommandError> > Resume(FileTransferToken token)
        {
            if (token.Status != TransferStatus.Cancelled)
            {
                return(CommandError.Custom("Only cancelled transfers can be resumed"));
            }

            if (token.Direction == TransferDirection.Upload)
            {
                var result = await FileTransferInitUpload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.Size, false, true);

                if (!result.Ok)
                {
                    return(result.Error);
                }
                var request = result.Value;
                token.ServerTransferId = request.ServerFileTransferId;
                token.SeekPosition     = (long)request.SeekPosition;
                token.Port             = request.Port;
                token.TransferKey      = request.FileTransferKey;
            }
            else             // Download
            {
                var result = await FileTransferInitDownload(token.ChannelId, token.Path, token.ChannelPassword, token.ClientTransferId, token.LocalStream.Position);

                if (!result.Ok)
                {
                    return(result.Error);
                }
                var request = result.Value;
                token.ServerTransferId = request.ServerFileTransferId;
                token.SeekPosition     = -1;
                token.Port             = request.Port;
                token.TransferKey      = request.FileTransferKey;
            }

            token.Status = TransferStatus.Waiting;

            return(await Transfer(token));
        }
        private async Task <R <FileTransferToken, CommandError> > Transfer(FileTransferToken token)
        {
            try
            {
                if (remoteAddress is null)
                {
                    token.Status = TransferStatus.Failed;
                    Log.Trace("Client is not connected. Transfer failed {@token}", token);
                    return(CommandError.ConnectionClosed);
                }
                if (token.Status != TransferStatus.Waiting)
                {
                    return(CommandError.Custom("Token is not open"));
                }
                token.Status = TransferStatus.Transfering;

                Log.Trace("Creating new file transfer connection to {0}", remoteAddress);
                using var client = new TcpClient(remoteAddress.AddressFamily);
                try { await client.ConnectAsync(remoteAddress.Address, token.Port); }
                catch (SocketException ex)
                {
                    Log.Warn(ex, "SocketException trying to connect to filetransfer port");
                    token.Status = TransferStatus.Failed;
                    return(CommandError.ConnectionClosed);
                }
                using var md5Dig = token.CreateMd5 ? MD5.Create() : null;
                using var stream = client.GetStream();
                byte[] keyBytes = Encoding.ASCII.GetBytes(token.TransferKey);
                await stream.WriteAsync(keyBytes, 0, keyBytes.Length);

                if (token.SeekPosition >= 0 && token.LocalStream.Position != token.SeekPosition)
                {
                    token.LocalStream.Seek(token.SeekPosition, SeekOrigin.Begin);
                }

                if (token.Direction == TransferDirection.Upload)
                {
                    // https://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,2a0f078c2e0c0aa8,references
                    const int bufferSize = 81920;
                    var       buffer     = new byte[bufferSize];
                    int       read;
                    md5Dig?.Initialize();
                    while ((read = await token.LocalStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
                    {
                        await stream.WriteAsync(buffer, 0, read);

                        md5Dig?.TransformBlock(buffer, 0, read, buffer, 0);
                    }
                    md5Dig?.TransformFinalBlock(Array.Empty <byte>(), 0, 0);
                    token.Md5Sum = md5Dig?.Hash;
                }
                else                 // Download
                {
                    // try to preallocate space
                    try { token.LocalStream.SetLength(token.Size); }
                    catch (NotSupportedException) { }

                    await stream.CopyToAsync(token.LocalStream);
                }
                if (token.Status == TransferStatus.Transfering && token.LocalStream.Position == token.Size)
                {
                    token.Status = TransferStatus.Done;
                    if (token.CloseStreamWhenDone)
                    {
#if NETSTANDARD2_0
                        token.LocalStream.Dispose();
#else
                        await token.LocalStream.DisposeAsync();
#endif
                    }
                }
            }
            catch (IOException ex)
            {
                Log.Debug(ex, "IOException during filetransfer");
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Exception during filetransfer");
            }
            finally
            {
                if (token.Status != TransferStatus.Done && token.Status != TransferStatus.Cancelled)
                {
                    token.Status = TransferStatus.Failed;
                }
            }
            if (token.Status == TransferStatus.Failed)
            {
                return(CommandError.Custom("Upload didn't finish"));
            }
            return(token);
        }
Esempio n. 12
0
        public LazyNotification?PushMessage(ReadOnlyMemory <byte> message)
        {
            var    msgSpan = message.Span;
            string notifyname;
            int    splitindex = msgSpan.IndexOf(AsciiSpace);

            if (splitindex < 0)
            {
                notifyname = msgSpan.TrimEnd(AsciiSpace).NewUtf8String();
            }
            else
            {
                notifyname = msgSpan.Slice(0, splitindex).NewUtf8String();
            }

            bool             hasEqual = notifyname.IndexOf('=') >= 0;
            NotificationType ntfyType;

            if (hasEqual || (ntfyType = findTypeOfNotification(notifyname)) == NotificationType.Unknown)
            {
                if (!hasEqual)
                {
                    Log.Debug("Maybe unknown notification: {0}", notifyname);
                }
                cmdLineBuffer = message;
                return(null);
            }

            var lineDataPart = splitindex < 0 ? ReadOnlySpan <byte> .Empty : msgSpan.Slice(splitindex);

            // if it's not an error it is a notification
            if (ntfyType != NotificationType.CommandError)
            {
                var notification = Deserializer.GenerateNotification(lineDataPart, ntfyType);
                if (notification is null)
                {
                    Log.Warn("Got unparsable message. ({0})", msgSpan.NewUtf8String());
                    return(null);
                }

                var lazyNotification = new LazyNotification(notification, ntfyType);
                var dependantList    = dependingBlocks[(int)ntfyType];
                if (dependantList != null)
                {
                    foreach (var item in dependantList)
                    {
                        item.SetNotification(lazyNotification);
                        if (item.DependsOn != null)
                        {
                            foreach (var otherDepType in item.DependsOn)
                            {
                                if (otherDepType == ntfyType)
                                {
                                    continue;
                                }
                                dependingBlocks[(int)otherDepType]?.Remove(item);
                            }
                        }
                    }
                    dependantList.Clear();
                }

                return(lazyNotification);
            }

            var result      = Deserializer.GenerateSingleNotification(lineDataPart, NotificationType.CommandError);
            var errorStatus = result is null?CommandError.Custom("Invalid Error code") : (CommandError)result;

            return(PushMessageInternal(errorStatus, ntfyType));
        }