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.pl 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/usr/bin/perl -w
  2. #
  3. # tftp to http proxy
  4. # Copyright 2003 Ken Yap
  5. # Released under GPL2
  6. #
  7. require 5.8.0; # needs constant and the pack Z format behaviour
  8. use bytes; # to forestall Unicode interpretation of strings
  9. use strict;
  10. use Getopt::Long;
  11. use Socket;
  12. use Sys::Hostname;
  13. use Sys::Syslog;
  14. use LWP;
  15. use POSIX 'setsid';
  16. use constant PROGNAME => 't2hproxy';
  17. use constant VERSION => '0.1';
  18. use constant ETH_DATA_LEN => 1500;
  19. use constant {
  20. TFTP_RRQ => 1, TFTP_WRQ => 2, TFTP_DATA => 3, TFTP_ACK => 4,
  21. TFTP_ERROR => 5, TFTP_OACK => 6
  22. };
  23. use constant {
  24. E_UNDEF => 0, E_FNF => 1, E_ACC => 2, E_DISK => 3, E_ILLOP => 4,
  25. E_UTID => 5, E_FEXIST => 6, E_NOUSER => 7
  26. };
  27. use vars qw($prefix $proxy $sockh $timeout %options $tsize $bsize);
  28. # We can't use die because xinetd will think something's wrong
  29. sub log_and_exit ($) {
  30. syslog('info', $_[0]);
  31. exit;
  32. }
  33. sub what_source ($) {
  34. my ($port, $saddr) = sockaddr_in($_[0]);
  35. my $host = gethostbyaddr($saddr, AF_INET);
  36. return ($host, $port);
  37. }
  38. sub send_error ($$$) {
  39. my ($iaddr, $error, $message) = @_;
  40. # error packets don't get acked
  41. send(STDOUT, pack('nna*', TFTP_ERROR, $error, $message), 0, $iaddr);
  42. }
  43. sub send_ack_retry ($$$$$) {
  44. my ($iaddr, $udptimeout, $maxretries, $blockno, $sendfunc) = @_;
  45. RETRY:
  46. while ($maxretries-- > 0) {
  47. &$sendfunc;
  48. my $rin = '';
  49. my $rout = '';
  50. vec($rin, fileno($sockh), 1) = 1;
  51. do {
  52. my ($fds, $timeleft) = select($rout = $rin, undef, undef, $udptimeout);
  53. last if ($fds <= 0);
  54. my $ack;
  55. my $theiripaddr = recv($sockh, $ack, 256, 0);
  56. # check it's for us
  57. if ($theiripaddr eq $iaddr) {
  58. my ($opcode, $ackblock) = unpack('nn', $ack);
  59. return (0) if ($opcode == TFTP_ERROR);
  60. # check that the right block was acked
  61. if ($ackblock == $blockno) {
  62. return (1);
  63. } else {
  64. syslog('info', "Resending block $blockno");
  65. next RETRY;
  66. }
  67. }
  68. # stray packet for some other server instance
  69. send_error($theiripaddr, E_UTID, 'Wrong TID');
  70. } while (1);
  71. }
  72. return (0);
  73. }
  74. sub handle_options ($$) {
  75. my ($iaddr, $operand) = @_;
  76. while ($operand ne '') {
  77. my ($key, $value) = unpack('Z*Z*', $operand);
  78. $options{$key} = $value;
  79. syslog('info', "$key=$value");
  80. $operand = substr($operand, length($key) + length($value) + 2);
  81. }
  82. my $optstr = '';
  83. if (exists($options{blksize})) {
  84. $bsize = $options{blksize};
  85. $bsize = 512 if ($bsize < 512);
  86. $bsize = 1432 if ($bsize > 1432);
  87. $optstr .= pack('Z*Z*', 'blksize', $bsize . '');
  88. }
  89. # OACK expects an ack for block 0
  90. log_and_exit('Abort received or retransmit limit reached, exiting')
  91. unless send_ack_retry($iaddr, 2, 5, 0,
  92. sub { send($sockh, pack('na*', TFTP_OACK, $optstr), 0, $iaddr); });
  93. }
  94. sub http_get ($) {
  95. my ($url) = @_;
  96. syslog('info', "GET $url");
  97. my $ua = LWP::UserAgent->new;
  98. $ua->timeout($timeout);
  99. $ua->proxy(['http', 'ftp'], $proxy) if (defined($proxy) and $proxy);
  100. my $req = HTTP::Request->new(GET => $url);
  101. my $res = $ua->request($req);
  102. return ($res->is_success, $res->status_line, $res->content_ref);
  103. }
  104. sub send_file ($$) {
  105. my ($iaddr, $contentref) = @_;
  106. my $blockno = 1;
  107. my $data;
  108. do {
  109. $blockno &= 0xffff;
  110. $data = substr($$contentref, ($blockno - 1) * $bsize, $bsize);
  111. # syslog('info', "Block $blockno length " . length($data));
  112. log_and_exit('Abort received or retransmit limit reached, exiting')
  113. unless send_ack_retry($iaddr, 2, 5, $blockno,
  114. sub { send($sockh, pack('nna*', TFTP_DATA, $blockno, $data), 0, $iaddr); });
  115. $blockno++;
  116. } while (length($data) >= $bsize);
  117. }
  118. sub do_rrq ($$) {
  119. my ($iaddr, $packetref) = @_;
  120. # fork and handle request in child so that *inetd can continue
  121. # to serve incoming requests
  122. defined(my $pid = fork) or log_and_exit("Can't fork: $!");
  123. exit if $pid; # parent exits
  124. setsid or log_and_exit("Can't start a new session: $!");
  125. socket(SOCK, PF_INET, SOCK_DGRAM, getprotobyname('udp')) or log_and_exit('Cannot create UDP socket');
  126. $sockh = *SOCK{IO};
  127. my ($opcode, $operand) = unpack('na*', $$packetref);
  128. my ($filename, $mode) = unpack('Z*Z*', $operand);
  129. syslog('info', "RRQ $filename $mode");
  130. my $length = length($filename) + length($mode) + 2;
  131. $operand = substr($operand, $length);
  132. handle_options($iaddr, $operand) if ($operand ne '');
  133. my ($success, $status_line, $result) = http_get($prefix . $filename);
  134. syslog('info', $status_line);
  135. if ($success) {
  136. send_file($iaddr, $result);
  137. } else {
  138. send_error($iaddr, E_FNF, $status_line);
  139. }
  140. }
  141. $prefix = 'http://localhost/';
  142. $timeout = 60;
  143. GetOptions('prefix=s' => \$prefix,
  144. 'proxy=s' => \$proxy,
  145. 'timeout=i' => \$timeout);
  146. $bsize = 512;
  147. openlog(PROGNAME, 'cons,pid', 'user');
  148. syslog('info', PROGNAME . ' version ' . VERSION);
  149. my $packet;
  150. my $theiriaddr = recv(STDIN, $packet, ETH_DATA_LEN, 0);
  151. my ($host, $port) = what_source($theiriaddr);
  152. syslog('info', "Connection from $host:$port");
  153. my $opcode = unpack('n', $packet);
  154. if ($opcode == TFTP_RRQ) {
  155. do_rrq($theiriaddr, \$packet);
  156. } else { # anything else is an error
  157. send_error($theiriaddr, E_ILLOP, 'Illegal operation');
  158. }
  159. exit 0;