/// <summary>
        /// Parse network finger command input. Will handle /W and remote commands. Assumes that the input is already trimmed
        /// correctly (e.g., it's a single input line).
        /// </summary>
        /// <param name="commandFromNetwork"></param>
        /// <returns></returns>
        public static ParsedFingerCommand ParseFromNetwork(string commandFromNetwork)
        {
            ParsedFingerCommand retval = null;
            bool hasWSwitch            = commandFromNetwork.StartsWith("/W");
            var  command = (hasWSwitch ? commandFromNetwork.Substring(2) : commandFromNetwork).Trim();

            // Remove extra whitespace chars in order to be more forgiving of bad input.
            if (command == "")
            {
                retval = new ParsedFingerCommand()
                {
                    FingerCommand   = CommandType.ListUser,
                    HasWSwitch      = hasWSwitch,
                    OriginalCommand = commandFromNetwork,
                };
            }
            else if (!command.Contains('@'))
            {
                retval = new ParsedFingerCommand()
                {
                    FingerCommand   = CommandType.ListUser,
                    HasWSwitch      = hasWSwitch,
                    User            = command,
                    OriginalCommand = commandFromNetwork,
                };
            }
            else
            {
                var split    = command.Split(new char[] { '@' });
                var hostlist = new String[split.Length - 1];
                Array.Copy(split, 1, hostlist, 0, hostlist.Length);
                retval = new ParsedFingerCommand()
                {
                    FingerCommand    = CommandType.ListRemote,
                    HasWSwitch       = hasWSwitch,
                    User             = split[0],
                    ReceivedHostList = hostlist,
                    OriginalCommand  = commandFromNetwork,
                };
            }

            if (retval == null)
            {
                retval = new ParsedFingerCommand()
                {
                    FingerCommand = CommandType.Error, OriginalCommand = commandFromNetwork
                };
            }
            return(retval);
        }
        /// <summary>
        /// Top-level parser for user interfaces. Can parse both @ style string ([email protected]) and finger urls.
        /// You can provide default values for port and the /W switch; these will be used instead of the system defaults
        /// when the format can't include them (e.g., there's no way to specify /W for [email protected]. The normal
        /// system default is false (no /W), but you can override that as needed.
        /// </summary>
        /// <param name="userString"></param>
        /// <param name="defaultService"></param>
        /// <param name="defaultWswitch"></param>
        /// <returns></returns>

        public static ParsedFingerCommand ParseFromUxString(string userString, string defaultService = null, bool defaultWswitch = false)
        {
            Uri  uri;
            bool isUri = Uri.TryCreate(userString, UriKind.Absolute, out uri);
            ParsedFingerCommand request = null;

            if (isUri)
            {
                request = ParsedFingerCommand.ParseFromUri(uri);
                if (uri.IsDefaultPort)
                {
                    request.SendToPort = defaultService;
                }
            }
            else
            {
                request            = ParsedFingerCommand.ParseFromAtString(userString, defaultWswitch);
                request.SendToPort = defaultService;
            }
            return(request);
        }
        public static ParsedFingerCommand ParseFromUri(Uri uri)
        {
            // See: https://tools.ietf.org/html/draft-ietf-uri-url-finger-03
            // finger://host[:port][/<request>]
            // examples from spec: finger://space.mit.edu/nasanews finger://status.nlak.net
            if (uri.Scheme.ToLowerInvariant() != "finger")
            {
                throw new Exception($"ERROR: FingerRequest requires URI that starts with finger://");
            }
            // The LocalPath will be //W user for urls like finger://example.com//W%20user
            bool   isW  = false;
            string user = uri.LocalPath;
            string port = DefaultService;

            if (uri.LocalPath.StartsWith("//W"))
            {
                user = uri.LocalPath.Substring(3).Trim();
                isW  = true;
            }
            else if (uri.LocalPath.StartsWith("/"))
            {
                user = uri.LocalPath.Substring(1);
            }
            // technical violation of the url spec: I allow finger://[email protected] and finger://[email protected]//W
            // In both cases, the user will be the one before the @sign/
            // If there's both a real path AND an user@ given, the user@ will take priority.
            if (!string.IsNullOrEmpty(uri.UserInfo))
            {
                user = uri.UserInfo;
            }
            if (!uri.IsDefaultPort)
            {
                port = uri.Port.ToString(); // is -1 by default for finger://
            }
            var retval = new ParsedFingerCommand(new HostName(uri.Host), user, isW);

            retval.SendToPort      = port;
            retval.OriginalCommand = uri.OriginalString;
            return(retval);
        }
        /// <summary>
        /// Converts input like [email protected] or @example.com or example.com into a request.
        /// Can also handle [email protected] example.com in which case [email protected] dnd host=example.com
        /// Does not handle uri (see FromUri for that)
        /// </summary>
        /// <param name="address"></param>
        /// <param name="wswitch"></param>
        /// <returns></returns>
        private static ParsedFingerCommand ParseFromAtString(String address, bool wswitch = false)
        {
            ParsedFingerCommand request = null;

            if (address.Contains(' '))
            {
                // is of the form [email protected]@subB.example.com example.com
                // where the request goes to example.com and the "user" is the long string with @ signs.
                var userSplit = address.Split(new char[] { ' ' }, 2);
                var user      = userSplit[0];
                var host      = new HostName(userSplit[1]);
                request = new ParsedFingerCommand(host, user, wswitch);
            }
            else
            {
                var userSplit = address.Split(new char[] { '@' }, 2);
                switch (userSplit.Length)
                {
                case 1:
                {
                    var host = new HostName(userSplit[0]);
                    request = new ParsedFingerCommand(host, "", wswitch);
                    break;
                }

                case 2:
                {
                    var user = userSplit[0];
                    var host = new HostName(userSplit[1]);
                    request = new ParsedFingerCommand(host, user, wswitch);
                    break;
                }
                }
            }
            return(request);
        }
Example #5
0
        public async Task <FingerResult> WriteAsync(ParsedFingerCommand request)
        {
            var data     = request.ToString();
            var datanice = data.Replace("\r\n", "");

            datanice = string.IsNullOrEmpty(datanice) ? "<blank string>" : datanice;

            var startTime = DateTime.UtcNow;

            try
            {
                var tcpSocket   = new StreamSocket();
                var connectTask = tcpSocket.ConnectAsync(request.SendToHost, request.SendToPort);

                var taskList = new Task[]
                {
                    connectTask.AsTask(),
                    Task.Delay(Options.MaxConnectTimeInMilliseconds)
                };
                var waitResult = await Task.WhenAny(taskList);

                if (waitResult == taskList[1])
                {
                    Stats.NExceptions++; // mark it as an exception -- it would have failed if we didn't time out
                    Log($"TIMEOUT while connecting to {request.SendToHost} {request.SendToPort}");
                    Log($"Unable to send command {datanice}\n");

                    var faildelta = DateTime.UtcNow.Subtract(startTime).TotalSeconds;
                    return(FingerResult.MakeFailed(SocketErrorStatus.ConnectionTimedOut, faildelta));
                }
                else
                {
                    // Connect is OK
                    if (!string.IsNullOrEmpty(data))
                    {
                        var dw = new DataWriter(tcpSocket.OutputStream);
                        dw.WriteString(data);
                        await dw.StoreAsync();

                        Log(ClientOptions.Verbosity.Normal, $"Finger sending command {datanice}\n");
                    }
                    Stats.NWrites++;

                    // Now read everything
                    var s      = tcpSocket.InputStream;
                    var buffer = new Windows.Storage.Streams.Buffer(1024 * 64); // read in lots of the data

                    string stringresult = "";
                    var    keepGoing    = true;
                    while (keepGoing)
                    {
                        try
                        {
                            var read = s.ReadAsync(buffer, buffer.Capacity, InputStreamOptions.Partial);

                            /* This is the syntax that the editor will suggest. There's a
                             * much simpler syntax (below) that's syntactic sugar over this.
                             * read.Progress = new AsyncOperationProgressHandler<IBuffer, uint>(
                             *  (operation, progress) =>
                             *  {
                             *      var err = operation.ErrorCode == null ? "null" : operation.ErrorCode.ToString();
                             *      Log(ClientOptions.Verbosity.Verbose, $"Daytime Progress count={progress} status={operation.Status} errorcode={err}");
                             *  });
                             */
                            read.Progress = (operation, progress) =>
                            {
                                var err = operation.ErrorCode == null ? "null" : operation.ErrorCode.ToString();
                                Log(ClientOptions.Verbosity.Verbose, $"Finger Progress count={progress} status={operation.Status} errorcode={err}");
                            };
                            var result = await read;
                            if (result.Length != 0)
                            {
                                var options       = BufferToString.ToStringOptions.ProcessCrLf | BufferToString.ToStringOptions.ProcessTab;
                                var partialresult = BufferToString.ToString(result, options);
                                stringresult += partialresult;
                                Log($"{partialresult}"); // This will be printed on the user's screen.
                            }
                            else
                            {
                                keepGoing = false;
                                Log(ClientOptions.Verbosity.Verbose, $"Read completed with zero bytes; closing");
                            }
                        }
                        catch (Exception ex2)
                        {
                            keepGoing = false;
                            Stats.NExceptions++;
                            Log($"EXCEPTION while reading: {ex2.Message} {ex2.HResult:X}");

                            var faildelta = DateTime.UtcNow.Subtract(startTime).TotalSeconds;
                            return(FingerResult.MakeFailed(ex2, faildelta));
                        }
                    }

                    var delta = DateTime.UtcNow.Subtract(startTime).TotalSeconds;
                    return(FingerResult.MakeSucceeded(stringresult, delta));
                }
            }
            catch (Exception ex)
            {
                Stats.NExceptions++;
                Log($"ERROR: Client: Writing {datanice} to {request.SendToHost} exception {ex.Message}");
                var delta = DateTime.UtcNow.Subtract(startTime).TotalSeconds;
                return(FingerResult.MakeFailed(ex, delta));
            }
        }
 public string OnListRemote(ParsedFingerCommand command)
 {
     return($"Request to forward finger is not allowed");
 }
 public string OnListUser(ParsedFingerCommand command)
 {
     return($"Unable to get information for user {command.User}");
 }
 public string OnListAll(ParsedFingerCommand command)
 {
     return("List all users is not allowed on this server");
 }
 public string OnError(ParsedFingerCommand command)
 {
     return($"ERROR: unknown command from {command.OriginalCommand}");
 }
        private async Task FingerTcpAsync(StreamSocket tcpSocket)
        {
            var    time  = DateTime.Now;
            string reply = "";

            var incomingText = await ReadIncomingCommandLineAsync(tcpSocket);

            var command = ParsedFingerCommand.ParseFromNetwork(incomingText);

            switch (command.FingerCommand)
            {
            case ParsedFingerCommand.CommandType.Error:
                reply = FingerRequestHandler.OnError(command);
                break;

            case ParsedFingerCommand.CommandType.ListAll:
                reply = FingerRequestHandler.OnListAll(command);
                break;

            case ParsedFingerCommand.CommandType.ListUser:
                switch (command.User)
                {
                case "stats":
                    reply = $"NConnection={Stats.NConnections}\nNBytesRead={Stats.NBytesRead}\nNExceptions={Stats.NExceptions}\n";
                    break;

                default:
                    reply = FingerRequestHandler.OnListUser(command);
                    break;
                }
                break;

            case ParsedFingerCommand.CommandType.ListRemote:
                reply = FingerRequestHandler.OnListRemote(command);
                break;
            }
            reply += "\n" + command.ToDebugString() + "\n" + command.ToReceivedNetworkCommand() + "****\r\n";


            //NOTE: here's how to write data using a DataWriter
            //var dw = new DataWriter(tcpSocket.OutputStream);
            //dw.WriteString(str);
            //await dw.StoreAsync();
            //await dw.FlushAsync(); // NOTE: this flush doesn't actually do anything useful.

            Stats.IncrementNResponses();
            uint totalBytesWrite = 0;
            var  writeBuffer     = Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(reply, Windows.Security.Cryptography.BinaryStringEncoding.Utf8);
            var  writeTask       = tcpSocket.OutputStream.WriteAsync(writeBuffer);
            var  bytesToWrite    = writeBuffer.Length;

            writeTask.Progress += (operation, progress) =>
            {
                totalBytesWrite = progress;
            };
            await tcpSocket.OutputStream.FlushAsync();


            var drainResult = await NetworkStreamUtilities.DrainStream(tcpSocket, Options.TcpReadTimeInMilliseconds);

            Stats.NBytesRead  += drainResult.NBytesRead;
            Stats.NExceptions += drainResult.NExceptions;
            if (drainResult.LogText != "")
            {
                Log(drainResult.LogText);
            }
            Log(ServerOptions.Verbosity.Verbose, $"SERVER: TCP Stream closing down the current writing socket");

            // Wait for the write buffer to be completely written, but only wait a short while.
            // The actual progress is limited because not all writes will trigger the progress indicator.
            // Works fine with no waiting (WriteTimeInMilliseconds set to -1)
            int currWait = 0;

            while (totalBytesWrite != bytesToWrite && currWait < Options.TcpWriteTimeInMilliseconds)
            {
                await Task.Delay(10);

                currWait += 10;
            }
            if (totalBytesWrite != bytesToWrite && Options.TcpWriteTimeInMilliseconds >= 0)
            {
                Log(ServerOptions.Verbosity.Verbose, $"SERVER: incomplete write {totalBytesWrite} of {bytesToWrite} wait time {Options.TcpWriteTimeInMilliseconds}");
            }

            if (Options.TcpPauseBeforeCloseTimeInMilliseconds >= 0)
            {
                await Task.Delay(Options.TcpPauseBeforeCloseTimeInMilliseconds);
            }
            tcpSocket.Dispose(); // The dispose is critical; without it the client won't ever finish reading our output
        }