mirror of
https://github.com/xcat2/xNBA.git
synced 2024-11-23 18:10:10 +00:00
509 lines
13 KiB
Java
509 lines
13 KiB
Java
|
/*
|
||
|
* TFTP to HTTP proxy in Java
|
||
|
*
|
||
|
* Copyright Ken Yap 2003
|
||
|
* Released under GPL2
|
||
|
*/
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.FileInputStream;
|
||
|
import java.io.BufferedInputStream;
|
||
|
import java.io.UnsupportedEncodingException;
|
||
|
import java.lang.String;
|
||
|
import java.lang.StringBuffer;
|
||
|
import java.lang.Thread;
|
||
|
import java.lang.NumberFormatException;
|
||
|
import java.net.DatagramPacket;
|
||
|
import java.net.DatagramSocket;
|
||
|
import java.net.InetAddress;
|
||
|
import java.net.SocketException;
|
||
|
import java.net.SocketTimeoutException;
|
||
|
import java.nio.Buffer;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.nio.BufferUnderflowException;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.Properties;
|
||
|
|
||
|
import org.apache.commons.httpclient.Credentials;
|
||
|
import org.apache.commons.httpclient.Header;
|
||
|
import org.apache.commons.httpclient.HostConfiguration;
|
||
|
import org.apache.commons.httpclient.HttpClient;
|
||
|
import org.apache.commons.httpclient.HttpException;
|
||
|
import org.apache.commons.httpclient.HttpMethod;
|
||
|
import org.apache.commons.httpclient.UsernamePasswordCredentials;
|
||
|
import org.apache.commons.httpclient.methods.GetMethod;
|
||
|
|
||
|
import org.apache.commons.logging.Log;
|
||
|
import org.apache.commons.logging.LogFactory;
|
||
|
|
||
|
/**
|
||
|
* Description of the Class
|
||
|
*
|
||
|
*@author ken
|
||
|
*@created 24 September 2003
|
||
|
*/
|
||
|
public class T2hproxy implements Runnable {
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static String NAME = T2hproxy.class.getName();
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static String VERSION = "0.1";
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static int MTU = 1500;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short TFTP_RRQ = 1;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short TFTP_DATA = 3;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short TFTP_ACK = 4;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short TFTP_ERROR = 5;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short TFTP_OACK = 6;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short ERR_NOFILE = 1;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static short ERR_ILLOP = 4;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static int MAX_RETRIES = 5;
|
||
|
/**
|
||
|
* TFTP timeout in milliseconds
|
||
|
*/
|
||
|
public final static int TFTP_ACK_TIMEOUT = 2000;
|
||
|
/**
|
||
|
* Description of the Field
|
||
|
*/
|
||
|
public final static int DEFAULT_PROXY_PORT = 3128;
|
||
|
|
||
|
private static Log log = LogFactory.getLog(T2hproxy.class);
|
||
|
/**
|
||
|
* The members below must be per thread and must not share any storage with
|
||
|
* the main thread
|
||
|
*/
|
||
|
private DatagramSocket responsesocket;
|
||
|
private DatagramPacket response;
|
||
|
private InetAddress iaddr;
|
||
|
private int port;
|
||
|
private byte[] req;
|
||
|
private String prefix;
|
||
|
private String proxy = null;
|
||
|
private int timeout;
|
||
|
private HashMap options = new HashMap();
|
||
|
private int blocksize = 512;
|
||
|
private HttpClient client = new HttpClient();
|
||
|
private HttpMethod method;
|
||
|
private BufferedInputStream bstream = null;
|
||
|
private String message;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Constructor for the T2hproxy object
|
||
|
*
|
||
|
*@param i Description of the Parameter
|
||
|
*@param p Description of the Parameter
|
||
|
*@param b Description of the Parameter
|
||
|
*@param pf Description of the Parameter
|
||
|
*@param pr Description of the Parameter
|
||
|
*@param t Timeout for HTTP GET
|
||
|
*/
|
||
|
public T2hproxy(InetAddress i, int p, byte[] b, String pf, String pr, int t) {
|
||
|
iaddr = i;
|
||
|
port = p;
|
||
|
// make a copy of the request buffer
|
||
|
req = new byte[b.length];
|
||
|
System.arraycopy(b, 0, req, 0, b.length);
|
||
|
prefix = pf;
|
||
|
// proxy can be null
|
||
|
proxy = pr;
|
||
|
timeout = t;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Extract an asciz string from bufer
|
||
|
*
|
||
|
*@param buffer Description of the Parameter
|
||
|
*@return The asciz value
|
||
|
*/
|
||
|
private String getAsciz(ByteBuffer buffer) {
|
||
|
StringBuffer s = new StringBuffer();
|
||
|
try {
|
||
|
byte b;
|
||
|
while ((b = buffer.get()) != 0) {
|
||
|
s.append((char) b);
|
||
|
}
|
||
|
} catch (BufferUnderflowException e) {
|
||
|
} finally {
|
||
|
return (s.toString());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Convert a string of digits to a number, invalid => 0
|
||
|
*
|
||
|
*@param s Description of the Parameter
|
||
|
*@return Description of the Return Value
|
||
|
*/
|
||
|
private int atoi(String s) {
|
||
|
if (s == null) {
|
||
|
return (0);
|
||
|
}
|
||
|
int value = 0;
|
||
|
try {
|
||
|
value = (new Integer(s)).intValue();
|
||
|
} catch (NumberFormatException e) {
|
||
|
}
|
||
|
return (value);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Wait for ack packet with timeout
|
||
|
*
|
||
|
*@return Return block number acked
|
||
|
*/
|
||
|
private int waitForAck() {
|
||
|
DatagramPacket ack = new DatagramPacket(new byte[MTU], MTU);
|
||
|
try {
|
||
|
do {
|
||
|
responsesocket.setSoTimeout(TFTP_ACK_TIMEOUT);
|
||
|
responsesocket.receive(ack);
|
||
|
} while (!ack.getAddress().equals(iaddr) || ack.getPort() != port);
|
||
|
} catch (SocketTimeoutException e) {
|
||
|
return (-1);
|
||
|
} catch (Exception e) {
|
||
|
log.info(e.toString(), e);
|
||
|
}
|
||
|
ByteBuffer buffer = ByteBuffer.wrap(ack.getData(), ack.getOffset(), ack.getLength() - ack.getOffset());
|
||
|
short op;
|
||
|
if ((op = buffer.getShort()) == TFTP_ACK) {
|
||
|
return ((int) buffer.getShort());
|
||
|
} else if (op == TFTP_ERROR) {
|
||
|
return (-2);
|
||
|
}
|
||
|
return (-3);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@param error Description of the Parameter
|
||
|
*@param message Description of the Parameter
|
||
|
*/
|
||
|
private void sendError(short error, String message) {
|
||
|
ByteBuffer buffer = ByteBuffer.wrap(response.getData());
|
||
|
buffer.putShort(TFTP_ERROR).putShort(error).put(message.getBytes());
|
||
|
response.setLength(buffer.position());
|
||
|
try {
|
||
|
responsesocket.send(response);
|
||
|
} catch (Exception e) {
|
||
|
log.info(e.toString(), e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@return Description of the Return Value
|
||
|
*/
|
||
|
private boolean sendOackRecvAck() {
|
||
|
ByteBuffer buffer = ByteBuffer.wrap(response.getData());
|
||
|
buffer.putShort(TFTP_OACK).put("blksize".getBytes()).put((byte) 0).put(String.valueOf(blocksize).getBytes()).put((byte) 0);
|
||
|
response.setLength(buffer.position());
|
||
|
int retry;
|
||
|
for (retry = 0; retry < MAX_RETRIES; retry++) {
|
||
|
try {
|
||
|
responsesocket.send(response);
|
||
|
} catch (Exception e) {
|
||
|
log.info(e.toString(), e);
|
||
|
}
|
||
|
if (waitForAck() == 0) {
|
||
|
log.debug("Ack received");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return (retry < MAX_RETRIES);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@param block Description of the Parameter
|
||
|
*@return Description of the Return Value
|
||
|
*/
|
||
|
private boolean sendDataBlock(int block) {
|
||
|
int retry;
|
||
|
for (retry = 0; retry < MAX_RETRIES; retry++) {
|
||
|
try {
|
||
|
responsesocket.send(response);
|
||
|
} catch (Exception e) {
|
||
|
log.info(e.toString(), e);
|
||
|
}
|
||
|
int ablock;
|
||
|
if ((ablock = waitForAck()) == block) {
|
||
|
log.debug("Ack received for " + ablock);
|
||
|
break;
|
||
|
} else if (ablock == -1) {
|
||
|
log.info("Timeout waiting for ack");
|
||
|
} else if (ablock == -2) {
|
||
|
return (false);
|
||
|
} else {
|
||
|
log.info("Unknown opcode from ack");
|
||
|
}
|
||
|
}
|
||
|
return (retry < MAX_RETRIES);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@param buffer Description of the Parameter
|
||
|
*@return Description of the Return Value
|
||
|
*/
|
||
|
private boolean handleOptions(ByteBuffer buffer) {
|
||
|
for (; ; ) {
|
||
|
String option = getAsciz(buffer);
|
||
|
String value = getAsciz(buffer);
|
||
|
if (option.equals("") || value.equals("")) {
|
||
|
break;
|
||
|
}
|
||
|
log.info(option + " " + value);
|
||
|
options.put(option, value);
|
||
|
}
|
||
|
blocksize = atoi((String) options.get("blksize"));
|
||
|
if (blocksize < 512) {
|
||
|
blocksize = 512;
|
||
|
}
|
||
|
if (blocksize > 1432) {
|
||
|
blocksize = 1432;
|
||
|
}
|
||
|
return (sendOackRecvAck());
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@param url Description of the Parameter
|
||
|
*/
|
||
|
private void makeStream(String url) {
|
||
|
// establish a connection within timeout milliseconds
|
||
|
client.setConnectionTimeout(timeout);
|
||
|
if (proxy != null) {
|
||
|
String[] hostport = proxy.split(":");
|
||
|
int port = DEFAULT_PROXY_PORT;
|
||
|
if (hostport.length > 1) {
|
||
|
port = atoi(hostport[1]);
|
||
|
if (port == 0) {
|
||
|
port = DEFAULT_PROXY_PORT;
|
||
|
}
|
||
|
}
|
||
|
log.info("Proxy is " + hostport[0] + ":" + port);
|
||
|
client.getHostConfiguration().setProxy(hostport[0], port);
|
||
|
}
|
||
|
// create a method object
|
||
|
method = new GetMethod(url);
|
||
|
method.setFollowRedirects(true);
|
||
|
method.setStrictMode(false);
|
||
|
try {
|
||
|
int status;
|
||
|
if ((status = client.executeMethod(method)) != 200) {
|
||
|
log.info(message = method.getStatusText());
|
||
|
return;
|
||
|
}
|
||
|
bstream = new BufferedInputStream(method.getResponseBodyAsStream());
|
||
|
} catch (HttpException he) {
|
||
|
message = he.getMessage();
|
||
|
} catch (IOException ioe) {
|
||
|
message = "Unable to get " + url;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Reads a block of data from URL stream
|
||
|
*
|
||
|
*@param stream Description of the Parameter
|
||
|
*@param data Description of the Parameter
|
||
|
*@param blocksize Description of the Parameter
|
||
|
*@param offset Description of the Parameter
|
||
|
*@return Number of bytes read
|
||
|
*/
|
||
|
private int readBlock(BufferedInputStream stream, byte[] data, int offset, int blocksize) {
|
||
|
int status;
|
||
|
int nread = 0;
|
||
|
while (nread < blocksize) {
|
||
|
try {
|
||
|
status = stream.read(data, offset + nread, blocksize - nread);
|
||
|
} catch (Exception e) {
|
||
|
return (-1);
|
||
|
}
|
||
|
if (status < 0) {
|
||
|
return (nread);
|
||
|
}
|
||
|
nread += status;
|
||
|
}
|
||
|
return (nread);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@param filename Description of the Parameter
|
||
|
*/
|
||
|
private void doRrq(String filename) {
|
||
|
String url = prefix + filename;
|
||
|
log.info("GET " + url);
|
||
|
makeStream(url);
|
||
|
if (bstream == null) {
|
||
|
log.info(message);
|
||
|
sendError(ERR_NOFILE, message);
|
||
|
return;
|
||
|
}
|
||
|
// read directly into send buffer to avoid buffer copying
|
||
|
byte[] data;
|
||
|
ByteBuffer buffer = ByteBuffer.wrap(data = response.getData());
|
||
|
// dummy puts to get start position of data
|
||
|
buffer.putShort(TFTP_DATA).putShort((short) 0);
|
||
|
int start = buffer.position();
|
||
|
int length;
|
||
|
int block = 1;
|
||
|
do {
|
||
|
length = readBlock(bstream, data, start, blocksize);
|
||
|
block &= 0xffff;
|
||
|
log.debug("Block " + block + " " + length);
|
||
|
// fill in the block number
|
||
|
buffer.position(0);
|
||
|
buffer.putShort(TFTP_DATA).putShort((short) block);
|
||
|
response.setLength(start + length);
|
||
|
if (!sendDataBlock(block)) {
|
||
|
break;
|
||
|
}
|
||
|
buffer.position(start);
|
||
|
block++;
|
||
|
} while (length >= blocksize);
|
||
|
log.info("Closing TFTP session");
|
||
|
// clean up the connection resources
|
||
|
method.releaseConnection();
|
||
|
method.recycle();
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Main processing method for the T2hproxy object
|
||
|
*/
|
||
|
public void run() {
|
||
|
ByteBuffer buffer = ByteBuffer.wrap(req);
|
||
|
buffer.getShort();
|
||
|
String filename = getAsciz(buffer);
|
||
|
String mode = getAsciz(buffer);
|
||
|
log.info(filename + " " + mode);
|
||
|
response = new DatagramPacket(new byte[MTU], MTU, iaddr, port);
|
||
|
try {
|
||
|
responsesocket = new DatagramSocket();
|
||
|
} catch (SocketException e) {
|
||
|
log.info(e.toString(), e);
|
||
|
return;
|
||
|
}
|
||
|
if (!handleOptions(buffer)) {
|
||
|
return;
|
||
|
}
|
||
|
doRrq(filename);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Description of the Method
|
||
|
*
|
||
|
*@param s Description of the Parameter
|
||
|
*@param r Description of the Parameter
|
||
|
*@param prefix Description of the Parameter
|
||
|
*@param proxy Description of the Parameter
|
||
|
*@param timeout Description of the Parameter
|
||
|
*/
|
||
|
public static void handleRequest(DatagramSocket s, DatagramPacket r, String prefix, String proxy, int timeout) {
|
||
|
log.info("Connection from " + r.getAddress().getCanonicalHostName() + ":" + r.getPort());
|
||
|
ByteBuffer buffer = ByteBuffer.wrap(r.getData(), r.getOffset(), r.getLength() - r.getOffset());
|
||
|
if (buffer.getShort() != TFTP_RRQ) {
|
||
|
DatagramPacket error = new DatagramPacket(new byte[MTU], MTU);
|
||
|
ByteBuffer rbuf = ByteBuffer.wrap(error.getData());
|
||
|
rbuf.putShort(TFTP_ERROR).putShort(ERR_ILLOP).put("Illegal operation".getBytes());
|
||
|
error.setLength(rbuf.position());
|
||
|
try {
|
||
|
s.send(error);
|
||
|
} catch (Exception e) {
|
||
|
log.info(e.toString(), e);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
// fork thread
|
||
|
new Thread(new T2hproxy(r.getAddress(), r.getPort(), r.getData(), prefix, proxy, timeout)).start();
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* The main program for the T2hproxy class
|
||
|
*
|
||
|
*@param argv The command line arguments
|
||
|
*@exception IOException Description of the Exception
|
||
|
*/
|
||
|
public static void main(String[] argv) throws IOException {
|
||
|
log.info(T2hproxy.NAME + "." + T2hproxy.VERSION);
|
||
|
int port = Integer.getInteger(T2hproxy.NAME + ".port", 69).intValue();
|
||
|
String prefix = System.getProperty(T2hproxy.NAME + ".prefix", "http://localhost/");
|
||
|
String proxy = System.getProperty(T2hproxy.NAME + ".proxy");
|
||
|
int timeout = Integer.getInteger(T2hproxy.NAME + ".timeout", 5000).intValue();
|
||
|
String propfile = System.getProperty(T2hproxy.NAME + ".properties");
|
||
|
if (propfile != null) {
|
||
|
FileInputStream pf = new FileInputStream(propfile);
|
||
|
Properties p = new Properties(System.getProperties());
|
||
|
p.load(pf);
|
||
|
// set the system properties
|
||
|
System.setProperties(p);
|
||
|
}
|
||
|
DatagramSocket requestsocket;
|
||
|
try {
|
||
|
requestsocket = new DatagramSocket(port);
|
||
|
} catch (SocketException e) {
|
||
|
log.info(e.toString(), e);
|
||
|
return;
|
||
|
}
|
||
|
DatagramPacket request = new DatagramPacket(new byte[MTU], MTU);
|
||
|
for (; ; ) {
|
||
|
try {
|
||
|
requestsocket.receive(request);
|
||
|
handleRequest(requestsocket, request, prefix, proxy, timeout);
|
||
|
} catch (Exception e) {
|
||
|
log.info(e.toString(), e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|