You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

T2hproxy.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. /*
  2. * TFTP to HTTP proxy in Java
  3. *
  4. * Copyright Ken Yap 2003
  5. * Released under GPL2
  6. */
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import java.io.FileInputStream;
  10. import java.io.BufferedInputStream;
  11. import java.io.UnsupportedEncodingException;
  12. import java.lang.String;
  13. import java.lang.StringBuffer;
  14. import java.lang.Thread;
  15. import java.lang.NumberFormatException;
  16. import java.net.DatagramPacket;
  17. import java.net.DatagramSocket;
  18. import java.net.InetAddress;
  19. import java.net.SocketException;
  20. import java.net.SocketTimeoutException;
  21. import java.nio.Buffer;
  22. import java.nio.ByteBuffer;
  23. import java.nio.BufferUnderflowException;
  24. import java.util.HashMap;
  25. import java.util.Properties;
  26. import org.apache.commons.httpclient.Credentials;
  27. import org.apache.commons.httpclient.Header;
  28. import org.apache.commons.httpclient.HostConfiguration;
  29. import org.apache.commons.httpclient.HttpClient;
  30. import org.apache.commons.httpclient.HttpException;
  31. import org.apache.commons.httpclient.HttpMethod;
  32. import org.apache.commons.httpclient.UsernamePasswordCredentials;
  33. import org.apache.commons.httpclient.methods.GetMethod;
  34. import org.apache.commons.logging.Log;
  35. import org.apache.commons.logging.LogFactory;
  36. /**
  37. * Description of the Class
  38. *
  39. *@author ken
  40. *@created 24 September 2003
  41. */
  42. public class T2hproxy implements Runnable {
  43. /**
  44. * Description of the Field
  45. */
  46. public final static String NAME = T2hproxy.class.getName();
  47. /**
  48. * Description of the Field
  49. */
  50. public final static String VERSION = "0.1";
  51. /**
  52. * Description of the Field
  53. */
  54. public final static int MTU = 1500;
  55. /**
  56. * Description of the Field
  57. */
  58. public final static short TFTP_RRQ = 1;
  59. /**
  60. * Description of the Field
  61. */
  62. public final static short TFTP_DATA = 3;
  63. /**
  64. * Description of the Field
  65. */
  66. public final static short TFTP_ACK = 4;
  67. /**
  68. * Description of the Field
  69. */
  70. public final static short TFTP_ERROR = 5;
  71. /**
  72. * Description of the Field
  73. */
  74. public final static short TFTP_OACK = 6;
  75. /**
  76. * Description of the Field
  77. */
  78. public final static short ERR_NOFILE = 1;
  79. /**
  80. * Description of the Field
  81. */
  82. public final static short ERR_ILLOP = 4;
  83. /**
  84. * Description of the Field
  85. */
  86. public final static int MAX_RETRIES = 5;
  87. /**
  88. * TFTP timeout in milliseconds
  89. */
  90. public final static int TFTP_ACK_TIMEOUT = 2000;
  91. /**
  92. * Description of the Field
  93. */
  94. public final static int DEFAULT_PROXY_PORT = 3128;
  95. private static Log log = LogFactory.getLog(T2hproxy.class);
  96. /**
  97. * The members below must be per thread and must not share any storage with
  98. * the main thread
  99. */
  100. private DatagramSocket responsesocket;
  101. private DatagramPacket response;
  102. private InetAddress iaddr;
  103. private int port;
  104. private byte[] req;
  105. private String prefix;
  106. private String proxy = null;
  107. private int timeout;
  108. private HashMap options = new HashMap();
  109. private int blocksize = 512;
  110. private HttpClient client = new HttpClient();
  111. private HttpMethod method;
  112. private BufferedInputStream bstream = null;
  113. private String message;
  114. /**
  115. * Constructor for the T2hproxy object
  116. *
  117. *@param i Description of the Parameter
  118. *@param p Description of the Parameter
  119. *@param b Description of the Parameter
  120. *@param pf Description of the Parameter
  121. *@param pr Description of the Parameter
  122. *@param t Timeout for HTTP GET
  123. */
  124. public T2hproxy(InetAddress i, int p, byte[] b, String pf, String pr, int t) {
  125. iaddr = i;
  126. port = p;
  127. // make a copy of the request buffer
  128. req = new byte[b.length];
  129. System.arraycopy(b, 0, req, 0, b.length);
  130. prefix = pf;
  131. // proxy can be null
  132. proxy = pr;
  133. timeout = t;
  134. }
  135. /**
  136. * Extract an asciz string from bufer
  137. *
  138. *@param buffer Description of the Parameter
  139. *@return The asciz value
  140. */
  141. private String getAsciz(ByteBuffer buffer) {
  142. StringBuffer s = new StringBuffer();
  143. try {
  144. byte b;
  145. while ((b = buffer.get()) != 0) {
  146. s.append((char) b);
  147. }
  148. } catch (BufferUnderflowException e) {
  149. } finally {
  150. return (s.toString());
  151. }
  152. }
  153. /**
  154. * Convert a string of digits to a number, invalid => 0
  155. *
  156. *@param s Description of the Parameter
  157. *@return Description of the Return Value
  158. */
  159. private int atoi(String s) {
  160. if (s == null) {
  161. return (0);
  162. }
  163. int value = 0;
  164. try {
  165. value = (new Integer(s)).intValue();
  166. } catch (NumberFormatException e) {
  167. }
  168. return (value);
  169. }
  170. /**
  171. * Wait for ack packet with timeout
  172. *
  173. *@return Return block number acked
  174. */
  175. private int waitForAck() {
  176. DatagramPacket ack = new DatagramPacket(new byte[MTU], MTU);
  177. try {
  178. do {
  179. responsesocket.setSoTimeout(TFTP_ACK_TIMEOUT);
  180. responsesocket.receive(ack);
  181. } while (!ack.getAddress().equals(iaddr) || ack.getPort() != port);
  182. } catch (SocketTimeoutException e) {
  183. return (-1);
  184. } catch (Exception e) {
  185. log.info(e.toString(), e);
  186. }
  187. ByteBuffer buffer = ByteBuffer.wrap(ack.getData(), ack.getOffset(), ack.getLength() - ack.getOffset());
  188. short op;
  189. if ((op = buffer.getShort()) == TFTP_ACK) {
  190. return ((int) buffer.getShort());
  191. } else if (op == TFTP_ERROR) {
  192. return (-2);
  193. }
  194. return (-3);
  195. }
  196. /**
  197. * Description of the Method
  198. *
  199. *@param error Description of the Parameter
  200. *@param message Description of the Parameter
  201. */
  202. private void sendError(short error, String message) {
  203. ByteBuffer buffer = ByteBuffer.wrap(response.getData());
  204. buffer.putShort(TFTP_ERROR).putShort(error).put(message.getBytes());
  205. response.setLength(buffer.position());
  206. try {
  207. responsesocket.send(response);
  208. } catch (Exception e) {
  209. log.info(e.toString(), e);
  210. }
  211. }
  212. /**
  213. * Description of the Method
  214. *
  215. *@return Description of the Return Value
  216. */
  217. private boolean sendOackRecvAck() {
  218. ByteBuffer buffer = ByteBuffer.wrap(response.getData());
  219. buffer.putShort(TFTP_OACK).put("blksize".getBytes()).put((byte) 0).put(String.valueOf(blocksize).getBytes()).put((byte) 0);
  220. response.setLength(buffer.position());
  221. int retry;
  222. for (retry = 0; retry < MAX_RETRIES; retry++) {
  223. try {
  224. responsesocket.send(response);
  225. } catch (Exception e) {
  226. log.info(e.toString(), e);
  227. }
  228. if (waitForAck() == 0) {
  229. log.debug("Ack received");
  230. break;
  231. }
  232. }
  233. return (retry < MAX_RETRIES);
  234. }
  235. /**
  236. * Description of the Method
  237. *
  238. *@param block Description of the Parameter
  239. *@return Description of the Return Value
  240. */
  241. private boolean sendDataBlock(int block) {
  242. int retry;
  243. for (retry = 0; retry < MAX_RETRIES; retry++) {
  244. try {
  245. responsesocket.send(response);
  246. } catch (Exception e) {
  247. log.info(e.toString(), e);
  248. }
  249. int ablock;
  250. if ((ablock = waitForAck()) == block) {
  251. log.debug("Ack received for " + ablock);
  252. break;
  253. } else if (ablock == -1) {
  254. log.info("Timeout waiting for ack");
  255. } else if (ablock == -2) {
  256. return (false);
  257. } else {
  258. log.info("Unknown opcode from ack");
  259. }
  260. }
  261. return (retry < MAX_RETRIES);
  262. }
  263. /**
  264. * Description of the Method
  265. *
  266. *@param buffer Description of the Parameter
  267. *@return Description of the Return Value
  268. */
  269. private boolean handleOptions(ByteBuffer buffer) {
  270. for (; ; ) {
  271. String option = getAsciz(buffer);
  272. String value = getAsciz(buffer);
  273. if (option.equals("") || value.equals("")) {
  274. break;
  275. }
  276. log.info(option + " " + value);
  277. options.put(option, value);
  278. }
  279. blocksize = atoi((String) options.get("blksize"));
  280. if (blocksize < 512) {
  281. blocksize = 512;
  282. }
  283. if (blocksize > 1432) {
  284. blocksize = 1432;
  285. }
  286. return (sendOackRecvAck());
  287. }
  288. /**
  289. * Description of the Method
  290. *
  291. *@param url Description of the Parameter
  292. */
  293. private void makeStream(String url) {
  294. // establish a connection within timeout milliseconds
  295. client.setConnectionTimeout(timeout);
  296. if (proxy != null) {
  297. String[] hostport = proxy.split(":");
  298. int port = DEFAULT_PROXY_PORT;
  299. if (hostport.length > 1) {
  300. port = atoi(hostport[1]);
  301. if (port == 0) {
  302. port = DEFAULT_PROXY_PORT;
  303. }
  304. }
  305. log.info("Proxy is " + hostport[0] + ":" + port);
  306. client.getHostConfiguration().setProxy(hostport[0], port);
  307. }
  308. // create a method object
  309. method = new GetMethod(url);
  310. method.setFollowRedirects(true);
  311. method.setStrictMode(false);
  312. try {
  313. int status;
  314. if ((status = client.executeMethod(method)) != 200) {
  315. log.info(message = method.getStatusText());
  316. return;
  317. }
  318. bstream = new BufferedInputStream(method.getResponseBodyAsStream());
  319. } catch (HttpException he) {
  320. message = he.getMessage();
  321. } catch (IOException ioe) {
  322. message = "Unable to get " + url;
  323. }
  324. }
  325. /**
  326. * Reads a block of data from URL stream
  327. *
  328. *@param stream Description of the Parameter
  329. *@param data Description of the Parameter
  330. *@param blocksize Description of the Parameter
  331. *@param offset Description of the Parameter
  332. *@return Number of bytes read
  333. */
  334. private int readBlock(BufferedInputStream stream, byte[] data, int offset, int blocksize) {
  335. int status;
  336. int nread = 0;
  337. while (nread < blocksize) {
  338. try {
  339. status = stream.read(data, offset + nread, blocksize - nread);
  340. } catch (Exception e) {
  341. return (-1);
  342. }
  343. if (status < 0) {
  344. return (nread);
  345. }
  346. nread += status;
  347. }
  348. return (nread);
  349. }
  350. /**
  351. * Description of the Method
  352. *
  353. *@param filename Description of the Parameter
  354. */
  355. private void doRrq(String filename) {
  356. String url = prefix + filename;
  357. log.info("GET " + url);
  358. makeStream(url);
  359. if (bstream == null) {
  360. log.info(message);
  361. sendError(ERR_NOFILE, message);
  362. return;
  363. }
  364. // read directly into send buffer to avoid buffer copying
  365. byte[] data;
  366. ByteBuffer buffer = ByteBuffer.wrap(data = response.getData());
  367. // dummy puts to get start position of data
  368. buffer.putShort(TFTP_DATA).putShort((short) 0);
  369. int start = buffer.position();
  370. int length;
  371. int block = 1;
  372. do {
  373. length = readBlock(bstream, data, start, blocksize);
  374. block &= 0xffff;
  375. log.debug("Block " + block + " " + length);
  376. // fill in the block number
  377. buffer.position(0);
  378. buffer.putShort(TFTP_DATA).putShort((short) block);
  379. response.setLength(start + length);
  380. if (!sendDataBlock(block)) {
  381. break;
  382. }
  383. buffer.position(start);
  384. block++;
  385. } while (length >= blocksize);
  386. log.info("Closing TFTP session");
  387. // clean up the connection resources
  388. method.releaseConnection();
  389. method.recycle();
  390. }
  391. /**
  392. * Main processing method for the T2hproxy object
  393. */
  394. public void run() {
  395. ByteBuffer buffer = ByteBuffer.wrap(req);
  396. buffer.getShort();
  397. String filename = getAsciz(buffer);
  398. String mode = getAsciz(buffer);
  399. log.info(filename + " " + mode);
  400. response = new DatagramPacket(new byte[MTU], MTU, iaddr, port);
  401. try {
  402. responsesocket = new DatagramSocket();
  403. } catch (SocketException e) {
  404. log.info(e.toString(), e);
  405. return;
  406. }
  407. if (!handleOptions(buffer)) {
  408. return;
  409. }
  410. doRrq(filename);
  411. }
  412. /**
  413. * Description of the Method
  414. *
  415. *@param s Description of the Parameter
  416. *@param r Description of the Parameter
  417. *@param prefix Description of the Parameter
  418. *@param proxy Description of the Parameter
  419. *@param timeout Description of the Parameter
  420. */
  421. public static void handleRequest(DatagramSocket s, DatagramPacket r, String prefix, String proxy, int timeout) {
  422. log.info("Connection from " + r.getAddress().getCanonicalHostName() + ":" + r.getPort());
  423. ByteBuffer buffer = ByteBuffer.wrap(r.getData(), r.getOffset(), r.getLength() - r.getOffset());
  424. if (buffer.getShort() != TFTP_RRQ) {
  425. DatagramPacket error = new DatagramPacket(new byte[MTU], MTU);
  426. ByteBuffer rbuf = ByteBuffer.wrap(error.getData());
  427. rbuf.putShort(TFTP_ERROR).putShort(ERR_ILLOP).put("Illegal operation".getBytes());
  428. error.setLength(rbuf.position());
  429. try {
  430. s.send(error);
  431. } catch (Exception e) {
  432. log.info(e.toString(), e);
  433. }
  434. return;
  435. }
  436. // fork thread
  437. new Thread(new T2hproxy(r.getAddress(), r.getPort(), r.getData(), prefix, proxy, timeout)).start();
  438. }
  439. /**
  440. * The main program for the T2hproxy class
  441. *
  442. *@param argv The command line arguments
  443. *@exception IOException Description of the Exception
  444. */
  445. public static void main(String[] argv) throws IOException {
  446. log.info(T2hproxy.NAME + "." + T2hproxy.VERSION);
  447. int port = Integer.getInteger(T2hproxy.NAME + ".port", 69).intValue();
  448. String prefix = System.getProperty(T2hproxy.NAME + ".prefix", "http://localhost/");
  449. String proxy = System.getProperty(T2hproxy.NAME + ".proxy");
  450. int timeout = Integer.getInteger(T2hproxy.NAME + ".timeout", 5000).intValue();
  451. String propfile = System.getProperty(T2hproxy.NAME + ".properties");
  452. if (propfile != null) {
  453. FileInputStream pf = new FileInputStream(propfile);
  454. Properties p = new Properties(System.getProperties());
  455. p.load(pf);
  456. // set the system properties
  457. System.setProperties(p);
  458. }
  459. DatagramSocket requestsocket;
  460. try {
  461. requestsocket = new DatagramSocket(port);
  462. } catch (SocketException e) {
  463. log.info(e.toString(), e);
  464. return;
  465. }
  466. DatagramPacket request = new DatagramPacket(new byte[MTU], MTU);
  467. for (; ; ) {
  468. try {
  469. requestsocket.receive(request);
  470. handleRequest(requestsocket, request, prefix, proxy, timeout);
  471. } catch (Exception e) {
  472. log.info(e.toString(), e);
  473. }
  474. }
  475. }
  476. }