using System; using System.Collections.Concurrent; using System.IO; using System.Net.Sockets; using System.Net; using System.Threading.Tasks; using System.Threading; using CheckCircuitCode.CommandLine; using CommandLine; namespace CheckCircuitCode { internal class Program { private enum CIRCUIT_STATUS : int { NONE = 0, AUTHENTICATION_FAILED, CIRCUIT_UNAVAILABLE, COMMUNICATION_ERROR } public static TcpClient TcpClient { get; set; } public static bool Verbose { get; private set; } static int Main(string[] args) { var CancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)); var CancellationToken = CancellationTokenSource.Token; return Parser.Default.ParseArguments(args) .MapResult(opts => RunConnectAndReturnExitCode(opts, CancellationToken), errs => 1); } private static int RunConnectAndReturnExitCode(ConnectOptions opts, CancellationToken cancellationToken) { Verbose = opts.Verbose; if (!IPEndPoint.TryParse(opts.Listen, out var listenEndPoint)) { Console.WriteLine("Bad listen HOST:PORT format."); return 1; } if (!IPEndPoint.TryParse(opts.Connect, out var connectEndPoint)) { Console.WriteLine("Bad connect HOST:PORT format."); return 1; } if (string.IsNullOrEmpty(opts.Password)) { Console.WriteLine("Bad password string."); return 1; } if (!int.TryParse(opts.Timeout, out var timeout)) { Console.WriteLine("Bad timeout value."); return 1; } var circuitStatus = Task.Run(() => StartClient(connectEndPoint, opts.Password, cancellationToken)); return (int)circuitStatus.Result; } private static async Task StartClient(IPEndPoint connect, string password, CancellationToken cancellationToken) { try { using (var tcpClient = new TcpClient()) { await tcpClient.ConnectAsync(connect.Address, connect.Port, cancellationToken); using (var tcpClientStream = tcpClient.GetStream()) { using (var streamReader = new StreamReader(tcpClientStream)) { using (var streamWriter = new StreamWriter(tcpClientStream)) { do { if (!await Authenticate(streamReader, streamWriter, password)) { if (Verbose) { Console.WriteLine("Authentication failed, please check OR port and password."); } return CIRCUIT_STATUS.AUTHENTICATION_FAILED; } do { try { if (await IsCircuitFormed(streamReader, streamWriter)) { return CIRCUIT_STATUS.NONE; } return CIRCUIT_STATUS.CIRCUIT_UNAVAILABLE; } catch (ArgumentException) { return CIRCUIT_STATUS.COMMUNICATION_ERROR; } } while (tcpClient.Connected && !cancellationToken.IsCancellationRequested); } while (tcpClient.Connected && !cancellationToken.IsCancellationRequested); } } } } } catch (Exception exception) { if (Verbose) { Console.WriteLine(exception); } return CIRCUIT_STATUS.COMMUNICATION_ERROR; } } public static async Task Authenticate(StreamReader sr, StreamWriter sw, string password) { var readLineTask = sr.ReadLineAsync(); await sw.WriteLineAsync($"AUTHENTICATE \"{password}\""); await sw.FlushAsync(); var readLine = await readLineTask; return string.Equals(readLine, @"250 OK", StringComparison.Ordinal); } public static async Task IsCircuitFormed(StreamReader sr, StreamWriter sw) { var readLineTask = sr.ReadLineAsync(); await sw.WriteLineAsync(@"GETINFO status/circuit-established"); await sw.FlushAsync(); var readLine = await readLineTask; var success = string.Equals(readLine, @"250-status/circuit-established=1", StringComparison.Ordinal); readLine = await sr.ReadLineAsync(); if (string.Equals(readLine, @"250 OK", StringComparison.Ordinal)) { return success; } throw new ArgumentException("Unable to read response from control port."); } public static async Task SendPayload(ConcurrentDictionary clients, string payload, CancellationToken CancellationToken) { foreach (var (handle, client) in clients) { if (CancellationToken.IsCancellationRequested) { throw new TaskCanceledException(); } try { if (!client.Connected) { continue; } using (var networkStream = client.GetStream()) { using (var sw = new StreamWriter(networkStream)) { await sw.WriteLineAsync(payload); } } } catch (Exception exception) { if (Verbose) { Console.WriteLine(exception); } } finally { clients.TryRemove(handle, out _); try { client.Close(); } catch (ObjectDisposedException exception) { if (Verbose) { Console.WriteLine(exception); } } } } } } }