mirror of
https://github.com/ipxe/ipxe
synced 2026-02-04 06:59:59 +03:00
Initial revision
This commit is contained in:
95
contrib/t2hproxy/README
Normal file
95
contrib/t2hproxy/README
Normal file
@@ -0,0 +1,95 @@
|
||||
T2hproxy
|
||||
|
||||
This is a TFTP to HTTP proxy. To the TFTP client it looks like a TFTP
|
||||
server. To the HTTP server it looks like a HTTP client. So you can store
|
||||
your boot files on the HTTP server. Or even create them with a CGI
|
||||
program. E.g. if you can get dhcpd to send a filename which has strings
|
||||
representing attributes of the client, as determined from the DHCP
|
||||
request, then you can get the CGI program to parse this and send the
|
||||
appropriate image, which might even be synthesised.
|
||||
|
||||
There are two versions of the proxy, in Perl and in Java.
|
||||
|
||||
1. The Perl version.
|
||||
|
||||
This is the original quick Perl hack conceived in a moment of madness.
|
||||
:-) Perl is great for prototyping.
|
||||
|
||||
To run it, you need Perl 5.8.0 or later and all the Perl modules listed
|
||||
at the top of the program installed. Edit and install the xinetd config
|
||||
file as /etc/xinetd.d/t2hproxy and restart xinetd. The prefix is the
|
||||
string that is prepended to all filenames to form the URL requested from
|
||||
the HTTP server. Remember you need the trailing / if the filenames don't
|
||||
start with /.
|
||||
|
||||
This is only a proof-of concept. It has these drawbacks at the moment:
|
||||
|
||||
+ (I don't consider this a draback, but some may.) It's started from
|
||||
xinetd because xinetd handles all the socket listening, IP address
|
||||
checking, rate limiting, etc.
|
||||
|
||||
+ It has no cache. Use a proxy to do the caching (there's a --proxy
|
||||
option). This also takes care of fetching from outside a firewall.
|
||||
|
||||
+ It reads the entire HTTP content into memory before serving. Ideally
|
||||
it should stream it from the HTTP server to minimise memory usage. This
|
||||
is a serious drawback for booting lots of clients. Each instance of the
|
||||
server will consume an amount of memory equal to the size of image
|
||||
loaded.
|
||||
|
||||
+ If the HTTP server is at the end of a slow link there is a delay
|
||||
before the first data block is sent. The client may timeout before
|
||||
then. Another reason for streaming, as this allows the first block to
|
||||
be sent sooner. A local cache primed with the images in advance may
|
||||
help. Using the blocksize option helps here because this causes the
|
||||
server to send the OACK to the client immediately before the data is
|
||||
fetched and this prevents it from starting up another connection.
|
||||
|
||||
+ The transfer size may not be obtainable from the HTTP headers in all
|
||||
cases, e.g. a CGI constructed image. This matters for clients that need
|
||||
the tsize extension, which is not supported at the moment.
|
||||
|
||||
If I'm feeling masochistic I may write a Java version, which should take
|
||||
care of the multi-threading and streaming.
|
||||
|
||||
2. The Java version
|
||||
|
||||
The main problem with the Perl version is that it does not stream the
|
||||
HTTP input but sucks it all in at once. As mentioned, this causes a
|
||||
delay as well as requiring memory to hold the image. I could fix this by
|
||||
doing the polling on the HTTP socket myself instead of letting LWP do
|
||||
it, but that's for later. Java has streaming facilities as well as
|
||||
threading and is also somewhat portable. So I decided to be masochistic
|
||||
and give it a go. But boy is Java bureaucratic.
|
||||
|
||||
You will need a Java 1.4 JRE, because I use the java.nio classes; and
|
||||
the commons-httpclient and commons-logging jars from the
|
||||
jakarta.apache.org project. As I understand it, there are several ways
|
||||
to get those jars on your classpath. One is to put it in the directory
|
||||
where your java extensions jars are kept, normally
|
||||
$JAVA_HOME/jre/lib/ext. But it may not be writable to you. Another is to
|
||||
set your $CLASSPATH variable to have those jars in the path. A third is
|
||||
to use the -cp option of the java interpreter, see the shell script
|
||||
runT2hproxy for details.
|
||||
|
||||
All the source is in one Java file. build.xml is a "Makefile" for ant to
|
||||
compile and jar it. You should then edit runT2proxy.sh as required, then
|
||||
start it. As with the Perl version, the prefix is what's prepended to
|
||||
the filenames requested by the TFTP client, and the proxy is the
|
||||
host:port string for the proxy if you are using one. On *ix you will
|
||||
need root permission to listen on ports below 1024 (TFTP is at 69 UDP by
|
||||
default).
|
||||
|
||||
Currently it logs to stderr, but you can change this by downloading and
|
||||
installing the log4j jar from jakarta.apache.org and instructing
|
||||
commons-logging to use that, with a command line property setting and a
|
||||
property file. Destinations could be syslog, or a file, or an event
|
||||
logger, or...; it's supposedly very flexible.
|
||||
|
||||
3. Licensing
|
||||
|
||||
All this code is GPLed. For details read the file COPYING found in the
|
||||
Etherboot top directory since it currently bundled with Etherboot. I
|
||||
don't see the point of including COPYING in every directory.
|
||||
|
||||
Ken Yap, October 2003
|
||||
508
contrib/t2hproxy/T2hproxy.java
Normal file
508
contrib/t2hproxy/T2hproxy.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
contrib/t2hproxy/build.xml
Normal file
19
contrib/t2hproxy/build.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Build file for T2hproxy
|
||||
-->
|
||||
<project name="T2hproxy" default="jar" basedir=".">
|
||||
<target name="compile">
|
||||
<javac fork="true" srcdir="." destdir="." />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar jarfile="T2hproxy.jar" basedir="."
|
||||
includes="T2hproxy.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="T2hproxy" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
</project>
|
||||
|
||||
15
contrib/t2hproxy/runT2proxy.sh
Executable file
15
contrib/t2hproxy/runT2proxy.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
# If the httpclient and logging jars are not in the standard directories
|
||||
# edit and uncomment
|
||||
# CP='-cp /usr/local/lib/commons-httpclient-2.0-rc1.jar:/usr/local/lib/commons-logging-api.jar:/usr/local/lib/commons-logging.jar'
|
||||
|
||||
# Edit and uncomment to use an alternate port
|
||||
# PORT='-DT2hproxy.port=1069'
|
||||
PREFIX='-DT2hproxy.prefix=http://localhost/'
|
||||
# Edit and uncomment to use a proxy
|
||||
# PROXY='-DT2hproxy.proxy=localhost:3128'
|
||||
# These T2hproxy properties can be put in a file and read in all at once
|
||||
# PROPERTIES='-DT2hproxy.properties=t2hproxy.prop
|
||||
|
||||
exec java -jar $CP $PORT $PREFIX $PROXY $PROPERTIES T2hproxy.jar
|
||||
174
contrib/t2hproxy/t2hproxy.pl
Executable file
174
contrib/t2hproxy/t2hproxy.pl
Executable file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# tftp to http proxy
|
||||
# Copyright 2003 Ken Yap
|
||||
# Released under GPL2
|
||||
#
|
||||
|
||||
require 5.8.0; # needs constant and the pack Z format behaviour
|
||||
|
||||
use bytes; # to forestall Unicode interpretation of strings
|
||||
use strict;
|
||||
|
||||
use Getopt::Long;
|
||||
use Socket;
|
||||
use Sys::Hostname;
|
||||
use Sys::Syslog;
|
||||
use LWP;
|
||||
use POSIX 'setsid';
|
||||
|
||||
use constant PROGNAME => 't2hproxy';
|
||||
use constant VERSION => '0.1';
|
||||
|
||||
use constant ETH_DATA_LEN => 1500;
|
||||
use constant {
|
||||
TFTP_RRQ => 1, TFTP_WRQ => 2, TFTP_DATA => 3, TFTP_ACK => 4,
|
||||
TFTP_ERROR => 5, TFTP_OACK => 6
|
||||
};
|
||||
use constant {
|
||||
E_UNDEF => 0, E_FNF => 1, E_ACC => 2, E_DISK => 3, E_ILLOP => 4,
|
||||
E_UTID => 5, E_FEXIST => 6, E_NOUSER => 7
|
||||
};
|
||||
|
||||
use vars qw($prefix $proxy $sockh $timeout %options $tsize $bsize);
|
||||
|
||||
# We can't use die because xinetd will think something's wrong
|
||||
sub log_and_exit ($) {
|
||||
syslog('info', $_[0]);
|
||||
exit;
|
||||
}
|
||||
|
||||
sub what_source ($) {
|
||||
my ($port, $saddr) = sockaddr_in($_[0]);
|
||||
my $host = gethostbyaddr($saddr, AF_INET);
|
||||
return ($host, $port);
|
||||
}
|
||||
|
||||
sub send_error ($$$) {
|
||||
my ($iaddr, $error, $message) = @_;
|
||||
# error packets don't get acked
|
||||
send(STDOUT, pack('nna*', TFTP_ERROR, $error, $message), 0, $iaddr);
|
||||
}
|
||||
|
||||
sub send_ack_retry ($$$$$) {
|
||||
my ($iaddr, $udptimeout, $maxretries, $blockno, $sendfunc) = @_;
|
||||
RETRY:
|
||||
while ($maxretries-- > 0) {
|
||||
&$sendfunc;
|
||||
my $rin = '';
|
||||
my $rout = '';
|
||||
vec($rin, fileno($sockh), 1) = 1;
|
||||
do {
|
||||
my ($fds, $timeleft) = select($rout = $rin, undef, undef, $udptimeout);
|
||||
last if ($fds <= 0);
|
||||
my $ack;
|
||||
my $theiripaddr = recv($sockh, $ack, 256, 0);
|
||||
# check it's for us
|
||||
if ($theiripaddr eq $iaddr) {
|
||||
my ($opcode, $ackblock) = unpack('nn', $ack);
|
||||
return (0) if ($opcode == TFTP_ERROR);
|
||||
# check that the right block was acked
|
||||
if ($ackblock == $blockno) {
|
||||
return (1);
|
||||
} else {
|
||||
syslog('info', "Resending block $blockno");
|
||||
next RETRY;
|
||||
}
|
||||
}
|
||||
# stray packet for some other server instance
|
||||
send_error($theiripaddr, E_UTID, 'Wrong TID');
|
||||
} while (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
sub handle_options ($$) {
|
||||
my ($iaddr, $operand) = @_;
|
||||
while ($operand ne '') {
|
||||
my ($key, $value) = unpack('Z*Z*', $operand);
|
||||
$options{$key} = $value;
|
||||
syslog('info', "$key=$value");
|
||||
$operand = substr($operand, length($key) + length($value) + 2);
|
||||
}
|
||||
my $optstr = '';
|
||||
if (exists($options{blksize})) {
|
||||
$bsize = $options{blksize};
|
||||
$bsize = 512 if ($bsize < 512);
|
||||
$bsize = 1432 if ($bsize > 1432);
|
||||
$optstr .= pack('Z*Z*', 'blksize', $bsize . '');
|
||||
}
|
||||
# OACK expects an ack for block 0
|
||||
log_and_exit('Abort received or retransmit limit reached, exiting')
|
||||
unless send_ack_retry($iaddr, 2, 5, 0,
|
||||
sub { send($sockh, pack('na*', TFTP_OACK, $optstr), 0, $iaddr); });
|
||||
}
|
||||
|
||||
sub http_get ($) {
|
||||
my ($url) = @_;
|
||||
syslog('info', "GET $url");
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout($timeout);
|
||||
$ua->proxy(['http', 'ftp'], $proxy) if (defined($proxy) and $proxy);
|
||||
my $req = HTTP::Request->new(GET => $url);
|
||||
my $res = $ua->request($req);
|
||||
return ($res->is_success, $res->status_line, $res->content_ref);
|
||||
}
|
||||
|
||||
sub send_file ($$) {
|
||||
my ($iaddr, $contentref) = @_;
|
||||
my $blockno = 1;
|
||||
my $data;
|
||||
do {
|
||||
$blockno &= 0xffff;
|
||||
$data = substr($$contentref, ($blockno - 1) * $bsize, $bsize);
|
||||
# syslog('info', "Block $blockno length " . length($data));
|
||||
log_and_exit('Abort received or retransmit limit reached, exiting')
|
||||
unless send_ack_retry($iaddr, 2, 5, $blockno,
|
||||
sub { send($sockh, pack('nna*', TFTP_DATA, $blockno, $data), 0, $iaddr); });
|
||||
$blockno++;
|
||||
} while (length($data) >= $bsize);
|
||||
}
|
||||
|
||||
sub do_rrq ($$) {
|
||||
my ($iaddr, $packetref) = @_;
|
||||
# fork and handle request in child so that *inetd can continue
|
||||
# to serve incoming requests
|
||||
defined(my $pid = fork) or log_and_exit("Can't fork: $!");
|
||||
exit if $pid; # parent exits
|
||||
setsid or log_and_exit("Can't start a new session: $!");
|
||||
socket(SOCK, PF_INET, SOCK_DGRAM, getprotobyname('udp')) or log_and_exit('Cannot create UDP socket');
|
||||
$sockh = *SOCK{IO};
|
||||
my ($opcode, $operand) = unpack('na*', $$packetref);
|
||||
my ($filename, $mode) = unpack('Z*Z*', $operand);
|
||||
syslog('info', "RRQ $filename $mode");
|
||||
my $length = length($filename) + length($mode) + 2;
|
||||
$operand = substr($operand, $length);
|
||||
handle_options($iaddr, $operand) if ($operand ne '');
|
||||
my ($success, $status_line, $result) = http_get($prefix . $filename);
|
||||
syslog('info', $status_line);
|
||||
if ($success) {
|
||||
send_file($iaddr, $result);
|
||||
} else {
|
||||
send_error($iaddr, E_FNF, $status_line);
|
||||
}
|
||||
}
|
||||
|
||||
$prefix = 'http://localhost/';
|
||||
$timeout = 60;
|
||||
GetOptions('prefix=s' => \$prefix,
|
||||
'proxy=s' => \$proxy,
|
||||
'timeout=i' => \$timeout);
|
||||
$bsize = 512;
|
||||
openlog(PROGNAME, 'cons,pid', 'user');
|
||||
syslog('info', PROGNAME . ' version ' . VERSION);
|
||||
my $packet;
|
||||
my $theiriaddr = recv(STDIN, $packet, ETH_DATA_LEN, 0);
|
||||
my ($host, $port) = what_source($theiriaddr);
|
||||
syslog('info', "Connection from $host:$port");
|
||||
my $opcode = unpack('n', $packet);
|
||||
if ($opcode == TFTP_RRQ) {
|
||||
do_rrq($theiriaddr, \$packet);
|
||||
} else { # anything else is an error
|
||||
send_error($theiriaddr, E_ILLOP, 'Illegal operation');
|
||||
}
|
||||
exit 0;
|
||||
29
contrib/t2hproxy/t2hproxy.xinetd
Normal file
29
contrib/t2hproxy/t2hproxy.xinetd
Normal file
@@ -0,0 +1,29 @@
|
||||
# Description: tftp to http proxy
|
||||
# A sample config file for xinetd, edit and put in /etc/xinetd.d
|
||||
# then killall -HUP xinetd, or restart xinetd
|
||||
|
||||
service t2hproxy
|
||||
{
|
||||
type = UNLISTED
|
||||
id = t2hproxy
|
||||
socket_type = dgram
|
||||
protocol = udp
|
||||
#
|
||||
# The pathname to where you have installed it
|
||||
#
|
||||
server = /usr/local/sbin/t2hproxy.pl
|
||||
#
|
||||
# If your filenames don't start with /, then the trailing
|
||||
# slash is needed
|
||||
#
|
||||
server_args = --prefix http://localhost/
|
||||
#
|
||||
# --proxy http://proxyhost:3128/ can also be appended
|
||||
#
|
||||
log_type = FILE /var/log/t2hproxy.log
|
||||
user = nobody
|
||||
wait = yes
|
||||
instances = 10
|
||||
disable = no
|
||||
port = 69
|
||||
}
|
||||
Reference in New Issue
Block a user