summaryrefslogtreecommitdiffstats
path: root/contrib/t2hproxy/T2hproxy.java
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/t2hproxy/T2hproxy.java')
-rw-r--r--contrib/t2hproxy/T2hproxy.java508
1 files changed, 508 insertions, 0 deletions
diff --git a/contrib/t2hproxy/T2hproxy.java b/contrib/t2hproxy/T2hproxy.java
new file mode 100644
index 00000000..cfe1d1a7
--- /dev/null
+++ b/contrib/t2hproxy/T2hproxy.java
@@ -0,0 +1,508 @@
+/*
+ * 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);
+ }
+ }
+ }
+}