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(); }
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); }
/// <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 }
// 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); }
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)); }
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); }
// 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); }
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)); }