I'm trying to implement my own ftp-server without any libraries on Java. For testing my server I use FTP-client FileZilla. The problem is that I can't send list of files in the current directory through data connection (or in other words I can't implement FTP command LIST).
I've tried both active and passive modes.
Here is the output of FileZilla:
Command: PASV
Response: 227 Entering Passive Mode (192,168,0,87,125,63)
Command: LIST
Response: 150 Opening BINARY mode data connection.
// Here I'm trying to send list of files through data connection
Response: 226 Transfer complete.
Error: Connection timed out
Error: Failed to retrieve directory listing
Here is my implementation of LIST and PASV commands:
public class FTPCommandSocket implements Runnable {
Socket command_socket;
Socket data_socket = null;
FTPDataSocket ftpds = null;
ServerSocket ss = null;
// ...
#Override
public void run() {
try {
// Send response to the client.
sendResponse("220 WELCOME\n");
while(from_client_command.read(input_b) != -1)
{
String input = new String(input_b);
String cmd = parseInput(input);
switch(cmd) {
// ... (other commands)
case Command.PASV:
sendResponse("227 Entering Passive Mode (192,168,0,87,125,63)\n");
ss = new ServerSocket(125 * 256 + 63);
data_socket = ss.accept();
ftpds = new FTPDataSocket(data_socket);
break;
case Command.LIST:
sendResponse("150 Opening BINARY mode data connection.\n");
ftpds.sendFilesList(cur_path);
Thread.sleep(500);
sendResponse("226 Transfer complete.\n");
break;
//... (other commands)
default:
sendResponse("502 Command is not implemented.\n");
}
}
} catch (...) {}
}
private void sendResponse(String string) throws IOException {
to_client_command.write(string.getBytes());
to_client_command.flush();
}
}
This is data connection class:
public class FTPDataSocket {
Socket data_socket;
OutputStream to_client_data;
InputStream from_client_data;
private final byte[] input_b = new byte[15000];
public FTPDataSocket(Socket s) throws IOException {
this.data_socket = s;
from_client_data = data_socket.getInputStream();
to_client_data = data_socket.getOutputStream();
}
public void sendFilesList(String path) throws IOException {
// Debug version: there are no real files yet.
sendResponse("-rwxr-xr-x 1 100 100 14757 a.out\r\n");
}
private void sendResponse(String string) throws IOException {
to_client_data.write(string.getBytes());
to_client_data.flush();
System.out.print(string);
}
}
Do you have any ideas, what I'm doing wrong?
Thank you in advance!
The problem was that socket was not closed after sending data.
Related
I want to control the bukkit server through the spring web application.
For example, send a command to the console, receive his response, etc
I'm trying to figure out a way, but I can't find a good one.
How shall I do it?
Even if third-party plugins are imported through the database, I want to find a way to do basic bukkit control.
First, you need to decide how to send the request to the server. It seems to me that in your case, the easiest is run the built-in java web server (HttpServer) to receive commands, and then process them.
If you need synchronous actions, then you can always do callSyncMethod
To receive command output, simply create your own implementation of CommandSender with overridden sendMessage methods
For example, how do command execution endpoint
JavaPlugin plugin = /** get plugin **/;
HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 8001), 0);
server.createContext("/executeCommand", exchange -> {
if (!exchange.getRequestMethod().equals("POST")) {
exchange.getResponseBody().write("Method not supported".getBytes(StandardCharsets.UTF_8));
return;
}
// In this example body is command
String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
StringBuilder builder = new StringBuilder();
// You also need override many another methods to compile code,but just leave it empty
CommandSender sender = new CommandSender() {
#Override
public void sendMessage(#NotNull String message) {
builder.append(message);
}
#Override
public void sendMessage(#NotNull String... messages) {
for (String message : messages) {
builder.append(message + "\n");
}
}
#Override
public boolean isOp() {
return true;
}
#Override
public boolean hasPermission(#NotNull String name) {
return true;
}
#Override
public #NotNull String getName() {
return "WebServerExecutor";
}
};
// Waiting command execute finish
Bukkit.getScheduler().callSyncMethod(plugin, () -> Bukkit.dispatchCommand(sender, body)).get();
byte[] response = builder.toString().getBytes(StandardCharsets.UTF_8);
exchange.getResponseBody().write(response);
});
server.start()
I have written an SNMP agent and registered a managed object (created/set a value of an MIB OID).
When I retrieve this value using SNMPv2c, the value is returned correctly - the PDU from ResponseEvent.getResponse has type=GET and the variable bindings have expected data - correct OID etc.
When I retrieve this value using SNMPv3 and user authentication, the value is not returned correctly - the PDU from ResponseEvent.getResponse has type=REPORT and the variable bindings have
a different OID from that in the request - from what I've read so far this indicates a config/authentication error.
Below is sample code (snippets) used for client & agent - please can you inform me how to create agent & client - where I'm going wrong?
// TestSNMPAgent:
public class TestSNMPAgent {
private OID sysDescr = new OID("1.3.6.1.2.1.1.1.0");
...
public static void main(String[] args) throws IOException {
TestSNMPAgent agent = new TestSNMPAgent();
agent.init("0.0.0.0/4071");
private void init(String agentIp) throws IOException {
agent = new SNMPAgent(agentIp);
agent.start();
agent.unregisterManagedObject(agent.getSnmpv2MIB());
agent.registerManagedObject(new MOScalar(oid,
MOAccessImpl.ACCESS_READ_WRITE,
getVariable(value),sysDescr,
"1")));
...
}
}
// SNMPAgent:
public class SNMPAgent extends BaseAgent {
...
#Override
protected void addUsmUser(USM arg0) {
UsmUser user = new UsmUser(new OctetString("SHADES"),
AuthSHA.ID,
new OctetString("SHADESAuthPassword"),
PrivDES.ID,
new OctetString("SHADESPrivPassword"));
}
#Override
protected void addViews(VacmMIB vacm) {
vacm.addGroup(SecurityModel.SECURITY_MODEL_USM,
new OctetString("SHADES"),
new OctetString("v3group"),
StorageType.nonVolatile);
vacm.addAccess(new OctetString("v3group"), new OctetString(),
SecurityModel.SECURITY_MODEL_USM,
SecurityLevel.NOAUTH_NOPRIV, VacmMIB.vacmExactMatch,
new OctetString("fullReadView"),
new OctetString("fullWriteView"),
new OctetString("fullNotifyView"),
StorageType.nonVolatile);
}
public void registerManagedObject(ManagedObject mo) {
try {
server.register(mo, null);
} catch (DuplicateRegistrationException ex) {
throw new RuntimeException(ex);
}
}
// TestSNMPMgr
public class TestSNMPMgr {
public static void main(String[] args) throws IOException {
TestSNMPMgr client = new TestSNMPMgr();
client.init();
}
public void init() {
SNMPMgr client = new SNMPMgr();
client.start();
// Get back Value which is set
String value = client.getAsString(new OID("1.3.6.1.2.1.1.1.0"));
}
}
// SNMPMgr
public class SNMPMgr {
Snmp snmp = null;
Address address = null;
public SNMPMgr()
{
address = "1.3.6.1.2.1.1.1.0";
}
/**
* Start the Snmp session. If you forget the listen() method you will not
* get any answers because the communication is asynchronous
* and the listen() method listens for answers.
* #throws IOException
*/
public void start() throws IOException {
address = GenericAddress.parse("udp:127.0.0.1/4701");
TransportMapping transport = new DefaultUdpTransportMapping();
snmp = new Snmp(transport);
USM usm = new USM(SecurityProtocols.getInstance(),
new OctetString(MPv3.createLocalEngineID()), 0);
SecurityModels.getInstance().addSecurityModel(usm);
transport.listen();
}
public void end() {
try {
snmp.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Method which takes a single OID and returns the response from the agent as a String.
* #param oid
* #return
* #throws IOException
*/
public String getAsString(OID oid) throws IOException {
ResponseEvent event = get(new OID[] { oid });
return event.getResponse().get(0).getVariable().toString();
}
public ResponseEvent get(OID oids[]) throws IOException {
PDU pdu = new ScopedPDU();
for (OID oid : oids) {
pdu.add(new VariableBinding(oid));
}
pdu.setType(PDU.GET);
// add user to the USM
snmp.getUSM().addUser(new OctetString("SHADES"),
new UsmUser(new OctetString("SHADES"),
AuthSHA.ID,
new OctetString("SHADESAuthPassword"),
PrivDES.ID,
new OctetString("SHADESPrivPassword")));
// send the PDU
ResponseEvent event = snmp.send(pdu, getTarget(), null);
if(event != null) {
return event;
}
throw new RuntimeException("GET timed out");
}
/**
* This method returns a Target, which contains information about
* where the data should be fetched and how.
* #return
*/
private UserTarget getTarget() {
UserTarget target = new UserTarget();
target.setAddress(address);
target.setRetries(1);
target.setTimeout(5000);
target.setVersion(SnmpConstants.version3);
target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
target.setSecurityName(new OctetString("SHADES"));
return target;
}
}
The OID in the Report PDU should tell you what is happening. Under typical circumstances there will be one or two (or one of two) request/report exchanges to establish initial SNMPv3 communications between manager and agent (or, rather, non-authoritative and authoritative engines, respectively).
The first is typically a usmStatUnknownEngineIDs report that allows the manager to discover the agent's Engine ID (needed for key localization/etc.) and will happen if you don't specify the proper Engine ID in the initial request. The second/other happens if using auth/noPriv or auth/priv level security, and that is usmStatsNotInTimeWindows, which is sent if the request doesn't specify Engine Boots/Engine Time values within proper range of the agent's values. These values prevent message replay attacks by making requests no longer valid if they fall out of the time window, and the manager typically doesn't know what they are until it receives them from the agent by way of a Report PDU.
After the manager has the proper Engine ID, Boots, and Time, and has localized keys to the Engine ID if necessary, then the normal request/response exchange can proceed as expected. Some SNMP APIs will take care of this exchange for you so you just send your request and get the eventual result after the exchange. It would seem that SNMP4j doesn't and you may have to handle it yourself if it's one of these reports.
If it's not one of these reports, then you likely have a mismatch in configuration.
We have a groovy singleton that uses PoolingHttpClientConnectionManager(httpclient:4.3.6) with a pool size of 200 to handle very high concurrent connections to a search service and processes the xml response.
Despite having specified timeouts, it freezes about once a month but runs perfectly fine the rest of the time.
The groovy singleton below. The method retrieveInputFromURL seems to block on client.execute(get);
#Singleton(strict=false)
class StreamManagerUtil {
// Instantiate once and cache for lifetime of Signleton class
private static PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
private static CloseableHttpClient client;
private static final IdleConnectionMonitorThread staleMonitor = new IdleConnectionMonitorThread(connManager);
private int warningLimit;
private int readTimeout;
private int connectionTimeout;
private int connectionFetchTimeout;
private int poolSize;
private int routeSize;
PropertyManager propertyManager = PropertyManagerFactory.getInstance().getPropertyManager("sebe.properties")
StreamManagerUtil() {
// Initialize all instance variables in singleton from properties file
readTimeout = 6
connectionTimeout = 6
connectionFetchTimeout =6
// Pooling
poolSize = 200
routeSize = 50
// Connection pool size and number of routes to cache
connManager.setMaxTotal(poolSize);
connManager.setDefaultMaxPerRoute(routeSize);
// ConnectTimeout : time to establish connection with GSA
// ConnectionRequestTimeout : time to get connection from pool
// SocketTimeout : waiting for packets form GSA
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(connectionTimeout * 1000)
.setConnectionRequestTimeout(connectionFetchTimeout * 1000)
.setSocketTimeout(readTimeout * 1000).build();
// Keep alive for 5 seconds if server does not have keep alive header
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
#Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
HeaderElementIterator it = new BasicHeaderElementIterator
(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase
("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return 5 * 1000;
}
};
// Close all connection older than 5 seconds. Run as separate thread.
staleMonitor.start();
staleMonitor.join(1000);
client = HttpClients.custom().setDefaultRequestConfig(config).setKeepAliveStrategy(myStrategy).setConnectionManager(connManager).build();
}
private retrieveInputFromURL (String categoryUrl, String xForwFor, boolean isXml) throws Exception {
URL url = new URL( categoryUrl );
GPathResult searchResponse = null
InputStream inputStream = null
HttpResponse response;
HttpGet get;
try {
long startTime = System.nanoTime();
get = new HttpGet(categoryUrl);
response = client.execute(get);
int resCode = response.getStatusLine().getStatusCode();
if (xForwFor != null) {
get.setHeader("X-Forwarded-For", xForwFor)
}
if (resCode == HttpStatus.SC_OK) {
if (isXml) {
extractXmlString(response)
} else {
StringBuffer buffer = buildStringFromResponse(response)
return buffer.toString();
}
}
}
catch (Exception e)
{
throw e;
}
finally {
// Release connection back to pool
if (response != null) {
EntityUtils.consume(response.getEntity());
}
}
}
private extractXmlString(HttpResponse response) {
InputStream inputStream = response.getEntity().getContent()
XmlSlurper slurper = new XmlSlurper()
slurper.setFeature("http://xml.org/sax/features/validation", false)
slurper.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
slurper.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false)
slurper.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
return slurper.parse(inputStream)
}
private StringBuffer buildStringFromResponse(HttpResponse response) {
StringBuffer buffer= new StringBuffer();
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line = "";
while ((line = rd.readLine()) != null) {
buffer.append(line);
System.out.println(line);
}
return buffer
}
public class IdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread
(PoolingHttpClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
#Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
connMgr.closeExpiredConnections();
connMgr.closeIdleConnections(10, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// Ignore
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}
I also found found this in the log leading me to believe it happened on waiting for response data
java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(SocketInputStream.java:150) at java.net.SocketInputStream.read(SocketInputStream.java:121) at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
Findings thus far:
We are using java 1.8u25. There is an open issue on a similar scenario
https://bugs.openjdk.java.net/browse/JDK-8075484
HttpClient had a similar report https://issues.apache.org/jira/browse/HTTPCLIENT-1589 but this was fixed in
the 4.3.6 version we are using
Questions
Can this be a synchronisation issue? From my understanding even though the singleton is accessed by multiple threads, the only shared data is the cached CloseableHttpClient
Is there anything else fundamentally wrong with this code,approach that may be causing this behaviour?
I do not see anything obviously wrong with your code. I would strongly recommend setting SO_TIMEOUT parameter on the connection manager, though, to make sure it applies to all new socket at the creation time, not at the time of request execution.
I would also help to know what exactly 'freezing' means. Are worker threads getting blocked waiting to acquire connections from the pool or waiting for response data?
Please also note that worker threads can appear 'frozen' if the server keeps on sending bits of chunk coded data. As usual a wire / context log of the client session would help a lot
http://hc.apache.org/httpcomponents-client-4.3.x/logging.html
I am currently building a client app with a forms UI for a scanner device running Windows Mobile 6.5.
The client app needs to communicate via TCP async sockets with a console server app.
Server and client built using the following info:
Server & Client.
My dev/test environment is as follows:
Console server app running on windows 7 desktop.
The cradled device is connected via USB and Windows Mobile Device Center.
The mobile client app manages to connect to the server, send the message and receive a response back initially.
However when I try and send another message (new socket), the app fails. The new socket doesn't seem to be connected the second time around?
I get the following exception:
NullReferenceException
at
SocketClient.ReceiveCallback()at System.Net.LazyAsyncresult.InvokeCallback()
at
WorkerThread.doWork()...
Code follows:
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Collections.Generic;
using System.Windows.Forms;
namespace SeatScan
{
static class Program
{
public static string serverIP;
public static int serverPort;
public static string response;
public static string message = string.Empty;
/// <summary>
/// The main entry point for the application.
/// </summary>
[MTAThread]
static void Main()
{
serverIP = MobileConfiguration.Settings["ServerIP"];
serverPort = int.Parse(MobileConfiguration.Settings["ServerPort"]);
Application.Run(new frmLogin());
}
public static void SendMessage(string message)
{
SocketClient.StartClient(serverIP, serverPort, message);
response = SocketClient.response;
}
}
static class SocketClient
{
// ManualResetEvent instances signal completion.
private static ManualResetEvent connectDone = new ManualResetEvent(false);
private static ManualResetEvent sendDone = new ManualResetEvent(false);
private static ManualResetEvent receiveDone = new ManualResetEvent(false);
// The response from the remote device.
public static string response = String.Empty;
public static void StartClient(string serverIP, int serverPort, string message)
{
response = String.Empty;
// Connect to a remote device.
try
{
// Establish the remote endpoint for the socket.
IPAddress ipAddress = IPAddress.Parse(serverIP);
IPEndPoint remoteEP = new IPEndPoint(ipAddress, serverPort);
// Create a TCP/IP socket.
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//socket.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.DontLinger, false);
//socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
// Connect to the remote endpoint.
socket.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), socket);
connectDone.WaitOne();
MessageBox.Show("connect=" + socket.Connected, "Connecting?");
// Send test data to the remote device.
Send(socket, message);
sendDone.WaitOne();
// Receive the response from the remote device.
Receive(socket);
receiveDone.WaitOne();
// Release the socket.
socket.Shutdown(SocketShutdown.Both);
socket.Close();
socket = null;
}
catch (Exception e)
{
//response = e.Message;
//Console.WriteLine(e.ToString());
MessageBox.Show(e.Message.ToString(), "StartClient");
}
}
private static void ConnectCallback(IAsyncResult ar)
{
try
{
// Retrieve the socket from the state object.
Socket client = (Socket)ar.AsyncState;
// Complete the connection.
client.EndConnect(ar);
//Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString());
//MessageBox.Show("Socket connected to {0}", client.RemoteEndPoint.ToString());
// Signal that the connection has been made.
connectDone.Set();
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
MessageBox.Show(e.Message.ToString(), "ConnectCallback");
}
}
private static void Receive(Socket client)
{
try
{
// Create the state object.
StateObject state = new StateObject();
state.workSocket = client;
// Begin receiving the data from the remote device.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
MessageBox.Show(e.Message.ToString(), "Receive");
}
}
private static void ReceiveCallback(IAsyncResult ar)
{
try
{
// Retrieve the state object and the client socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;
// Read data from the remote device.
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
// Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
else
{
// All the data has arrived; put it in response.
if (state.sb.Length > 1)
{
response = state.sb.ToString();
}
// Signal that all bytes have been received.
receiveDone.Set();
}
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
MessageBox.Show(e.Message.ToString() + e.InnerException.Message, "ReceiveCallback");
}
}
private static void Send(Socket client, String data)
{
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.ASCII.GetBytes(data);
// Begin sending the data to the remote device.
client.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), client);
}
private static void SendCallback(IAsyncResult ar)
{
try
{
// Retrieve the socket from the state object.
Socket client = (Socket)ar.AsyncState;
// Complete sending the data to the remote device.
int bytesSent = client.EndSend(ar);
//Console.WriteLine("Sent {0} bytes to server.", bytesSent);
// Signal that all bytes have been sent.
sendDone.Set();
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
MessageBox.Show(e.Message.ToString() + e.InnerException.Message, "SendCallback");
}
}
}
// State object for reading client data asynchronously
public class StateObject
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
}
}
Any help is appreciated.
Nevermind, I found the solution :)
This will teach me to copy paste sample code without fully understanding it.
It turns out, since I am reconnecting after first connect, I need to reset the state of the ManualResetEvents... Duh.
I needed to add:
connectDone.Reset();
sendDone.Reset();
receiveDone.Reset();
just before the line...
socket.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), socket);
I hope this helps someone as I lost a bit of hair figuring this one out...
I am writing telnet client for cisco router using apache.commons.net.telnet. But i have problem. Here is code sample:
static TelnetClient telnetClient = new TelnetClient();
public static void main(String[] args) throws IOException {
setOptionHandlers();
telnetClient.connect("192.168.127.100");
read();
telnetClient.disconnect();
}
private static void setOptionHandlers() throws IOException {
ArrayList<TelnetOptionHandler> optionHandlers =
new ArrayList<TelnetOptionHandler>();
optionHandlers.add(new TerminalTypeOptionHandler("VT100", false, false, true, false));
optionHandlers.add(new EchoOptionHandler(true, false, true, false));
optionHandlers.add(new SuppressGAOptionHandler(true, true, true, true));
for (TelnetOptionHandler handler : optionHandlers) {
try {
telnetClient.addOptionHandler(handler);
}
catch (InvalidTelnetOptionException e) {
System.err.println("Error registering option handler "
+ handler.getClass().getSimpleName());
}
}
}
public static void write(byte[] data) throws IOException {
telnetClient.getOutputStream().write(data);
telnetClient.getOutputStream().flush();
}
public static void read() throws IOException {
System.out.println("Read");
byte[] buff = new byte[1024];
int read;
if((read = telnetClient.getInputStream().read(buff)) > 0) {
System.out.println(new String(buff, 0, read));
}
System.out.println("read="+read);
}
In some cases it works correctly and shows prompt for password entering. But is other cases it works incorrectly - hangs by reading from telnet input stream. Run conditions are the same. Why do I get this situation?
If anyone has tips for writing cisco telnet client, i'll be glad to hear them!
I can reproduce this problem every time.
The problem can be worked-around by changing your read buffer size to 1 byte.
This accounts for why the readUntil() function from Looking for Java Telnet emulator works, at it simply calls read() for 1 byte.
That said, does this indicate a bug in org.apache.commons.net.telnet.TelnetClient?
Edit: Rolled back to an earlier version of Commons Net and the problem disappeared !