diff options
Diffstat (limited to 'contrib/t2hproxy')
-rw-r--r-- | contrib/t2hproxy/README | 95 | ||||
-rw-r--r-- | contrib/t2hproxy/T2hproxy.java | 508 | ||||
-rw-r--r-- | contrib/t2hproxy/build.xml | 19 | ||||
-rwxr-xr-x | contrib/t2hproxy/runT2proxy.sh | 15 | ||||
-rwxr-xr-x | contrib/t2hproxy/t2hproxy.pl | 174 | ||||
-rw-r--r-- | contrib/t2hproxy/t2hproxy.xinetd | 29 |
6 files changed, 840 insertions, 0 deletions
diff --git a/contrib/t2hproxy/README b/contrib/t2hproxy/README new file mode 100644 index 000000000..ce5e1e9ba --- /dev/null +++ b/contrib/t2hproxy/README @@ -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 diff --git a/contrib/t2hproxy/T2hproxy.java b/contrib/t2hproxy/T2hproxy.java new file mode 100644 index 000000000..cfe1d1a79 --- /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); + } + } + } +} diff --git a/contrib/t2hproxy/build.xml b/contrib/t2hproxy/build.xml new file mode 100644 index 000000000..5494ab96e --- /dev/null +++ b/contrib/t2hproxy/build.xml @@ -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> + diff --git a/contrib/t2hproxy/runT2proxy.sh b/contrib/t2hproxy/runT2proxy.sh new file mode 100755 index 000000000..d7fc0d2d2 --- /dev/null +++ b/contrib/t2hproxy/runT2proxy.sh @@ -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 diff --git a/contrib/t2hproxy/t2hproxy.pl b/contrib/t2hproxy/t2hproxy.pl new file mode 100755 index 000000000..4fc01781b --- /dev/null +++ b/contrib/t2hproxy/t2hproxy.pl @@ -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; diff --git a/contrib/t2hproxy/t2hproxy.xinetd b/contrib/t2hproxy/t2hproxy.xinetd new file mode 100644 index 000000000..ea6a03f1e --- /dev/null +++ b/contrib/t2hproxy/t2hproxy.xinetd @@ -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 +} |