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