156668Sshin# faithd, ruby version.  requires v6-enabled ruby.
256668Sshin#
356668Sshin# highly experimental (not working right at all) and very limited
456668Sshin# functionality.
556668Sshin#
6122679Sume# $Id: faithd.rb,v 1.1.2.4 1999/05/10 17:06:30 itojun Exp $
756668Sshin# $FreeBSD$
856668Sshin
956668Sshinrequire "socket"
1056668Sshinrequire "thread"
1156668Sshin
1256668Sshin# XXX should be derived from system headers
1356668SshinIPPROTO_IPV6 = 41
1456668SshinIPV6_FAITH = 29
1556668SshinDEBUG = true
1656668SshinDEBUG_LOOPBACK = true
1756668Sshin
1856668Sshin# TODO: OOB data handling
1956668Sshindef tcpcopy(s1, s2, m)
2056668Sshin  STDERR.print "tcpcopy #{s1} #{s2}\n" if DEBUG
2156668Sshin  buf = ""
2256668Sshin  while TRUE
2356668Sshin    begin
2456668Sshin      buf = s1.sysread(100)
2556668Sshin      s2.syswrite(buf)
2656668Sshin    rescue EOFError
2756668Sshin      break
2856668Sshin    rescue IOError
2956668Sshin      break
3056668Sshin    end
3156668Sshin  end
3256668Sshin  STDERR.print "tcpcopy #{s1} #{s2} finished\n" if DEBUG
3356668Sshin  s1.shutdown(0)
3456668Sshin  s2.shutdown(1)
3556668Sshinend
3656668Sshin
3756668Sshindef relay_ftp_passiveconn(s6, s4, dport6, dport4)
3856668Sshin  Thread.start do
3956668Sshin    d6 = TCPserver.open("::", dport6).accept
4056668Sshin    d4 = TCPsocket.open(s4.getpeer[3], dport4)
4156668Sshin    t = []
4256668Sshin    t[0] = Thread.start do
4356668Sshin      tcpcopy(d6, d4)
4456668Sshin    end
4556668Sshin    t[1] = Thread.start do
4656668Sshin      tcpcopy(d4, d6)
4756668Sshin    end
4856668Sshin    for i in t
4956668Sshin      i.join
5056668Sshin    end
5156668Sshin    d4.close
5256668Sshin    d6.close
5356668Sshin  end
5456668Sshinend
5556668Sshin
5656668Sshindef ftp_parse_2428(line)
5756668Sshin  if (line[0] != line[line.length - 1])
5856668Sshin    return nil
5956668Sshin  end
6056668Sshin  t = line.split(line[0 .. 0])	# as string
6156668Sshin  if (t.size != 4 || t[1] !~ /^[12]$/ || t[3] !~ /^\d+$/)
6256668Sshin    return nil
6356668Sshin  end
6456668Sshin  return t[1 .. 3]
6556668Sshinend
6656668Sshin
6756668Sshindef relay_ftp_command(s6, s4, state)
6856668Sshin  STDERR.print "relay_ftp_command start\n" if DEBUG
6956668Sshin  while TRUE
7056668Sshin    begin
7156668Sshin      STDERR.print "s6.gets\n" if DEBUG
7256668Sshin      line = s6.gets
7356668Sshin      STDERR.print "line is #{line}\n" if DEBUG
7456668Sshin      if line == nil
7556668Sshin	return nil
7656668Sshin      end
7756668Sshin
7856668Sshin      # translate then copy
7956668Sshin      STDERR.print "line is #{line}\n" if DEBUG
8056668Sshin      if (line =~ /^EPSV\r\n/i)
8156668Sshin	STDERR.print "EPSV -> PASV\n" if DEBUG
8256668Sshin	line = "PASV\n"
8356668Sshin	state = "EPSV"
8456668Sshin      elsif (line =~ /^EPRT\s+(.+)\r\n/i)
8556668Sshin	t = ftp_parse_2428($1)
8656668Sshin	if t == nil
8756668Sshin	  s6.puts "501 illegal parameter to EPRT\r\n"
8856668Sshin	  next
8956668Sshin	end
9056668Sshin
9156668Sshin	# some tricks should be here
9256668Sshin	s6.puts "501 illegal parameter to EPRT\r\n"
9356668Sshin	next
9456668Sshin      end
9556668Sshin      STDERR.print "fail: send #{line} as is\n" if DEBUG
9656668Sshin      s4.puts(line)
9756668Sshin      break
9856668Sshin    rescue EOFError
9956668Sshin      return nil
10056668Sshin    rescue IOError
10156668Sshin      return nil
10256668Sshin    end
10356668Sshin  end
10456668Sshin  STDERR.print "relay_ftp_command finish\n" if DEBUG
10556668Sshin  return state
10656668Sshinend
10756668Sshin
10856668Sshindef relay_ftp_status(s4, s6, state)
10956668Sshin  STDERR.print "relay_ftp_status start\n" if DEBUG
11056668Sshin  while TRUE
11156668Sshin    begin
11256668Sshin      line = s4.gets
11356668Sshin      if line == nil
11456668Sshin	return nil
11556668Sshin      end
11656668Sshin
11756668Sshin      # translate then copy
11856668Sshin      s6.puts(line)
11956668Sshin
12056668Sshin      next if line =~ /^\d\d\d-/
12156668Sshin      next if line !~ /^\d/
12256668Sshin
12356668Sshin      # special post-processing
12456668Sshin      case line
12556668Sshin      when /^221 /	# result to QUIT
12656668Sshin	s4.shutdown(0)
12756668Sshin	s6.shutdown(1)
12856668Sshin      end
12956668Sshin
13056668Sshin      break if (line =~ /^\d\d\d /)
13156668Sshin    rescue EOFError
13256668Sshin      return nil
13356668Sshin    rescue IOError
13456668Sshin      return nil
13556668Sshin    end
13656668Sshin  end
13756668Sshin  STDERR.print "relay_ftp_status finish\n" if DEBUG
13856668Sshin  return state
13956668Sshinend
14056668Sshin
14156668Sshindef relay_ftp(sock, name)
14256668Sshin  STDERR.print "relay_ftp(#{sock}, #{name})\n" if DEBUG
14356668Sshin  while TRUE
14456668Sshin    STDERR.print "relay_ftp(#{sock}, #{name}) accepting\n" if DEBUG
14556668Sshin    s = sock.accept
14656668Sshin    STDERR.print "relay_ftp(#{sock}, #{name}) accepted #{s}\n" if DEBUG
14756668Sshin    Thread.start do
14856668Sshin      threads = []
14956668Sshin      STDERR.print "accepted #{s} -> #{Thread.current}\n" if DEBUG
15056668Sshin      s6 = s
15156668Sshin      dest6 = s.addr[3]
15256668Sshin      if !DEBUG_LOOPBACK
15356668Sshin	t = s.getsockname.unpack("x8 x12 C4")
15456668Sshin	dest4 = "#{t[0]}.#{t[1]}.#{t[2]}.#{t[3]}"
15556668Sshin	port4 = s.addr[1]
15656668Sshin      else
15756668Sshin	dest4 = "127.0.0.1"
15856668Sshin	port4 = "ftp"
15956668Sshin      end
16056668Sshin      if DEBUG
16156668Sshin	STDERR.print "IPv6 dest: #{dest6}  IPv4 dest: #{dest4}\n" if DEBUG
16256668Sshin      end
16356668Sshin      STDERR.print "connect to #{dest4} #{port4}\n" if DEBUG
16456668Sshin      s4 = TCPsocket.open(dest4, port4)
16556668Sshin      STDERR.print "connected to #{dest4} #{port4}, #{s4.addr[1]}\n" if DEBUG
16656668Sshin      state = 0
16756668Sshin      while TRUE
16856668Sshin	# translate status line
16956668Sshin	state = relay_ftp_status(s4, s6, state)
17056668Sshin	break if state == nil
17156668Sshin	# translate command line
17256668Sshin	state = relay_ftp_command(s6, s4, state)
17356668Sshin	break if state == nil
17456668Sshin      end
17556668Sshin      STDERR.print "relay_ftp(#{sock}, #{name}) closing s4\n" if DEBUG
17656668Sshin      s4.close
17756668Sshin      STDERR.print "relay_ftp(#{sock}, #{name}) closing s6\n" if DEBUG
17856668Sshin      s6.close
17956668Sshin      STDERR.print "relay_ftp(#{sock}, #{name}) done\n" if DEBUG
18056668Sshin    end
18156668Sshin  end
18256668Sshin  STDERR.print "relay_ftp(#{sock}, #{name}) finished\n" if DEBUG
18356668Sshinend
18456668Sshin
18556668Sshindef relay_tcp(sock, name)
18656668Sshin  STDERR.print "relay_tcp(#{sock}, #{name})\n" if DEBUG
18756668Sshin  while TRUE
18856668Sshin    STDERR.print "relay_tcp(#{sock}, #{name}) accepting\n" if DEBUG
18956668Sshin    s = sock.accept
19056668Sshin    STDERR.print "relay_tcp(#{sock}, #{name}) accepted #{s}\n" if DEBUG
19156668Sshin    Thread.start do
19256668Sshin      threads = []
19356668Sshin      STDERR.print "accepted #{s} -> #{Thread.current}\n" if DEBUG
19456668Sshin      s6 = s
19556668Sshin      dest6 = s.addr[3]
19656668Sshin      if !DEBUG_LOOPBACK
19756668Sshin	t = s.getsockname.unpack("x8 x12 C4")
19856668Sshin	dest4 = "#{t[0]}.#{t[1]}.#{t[2]}.#{t[3]}"
19956668Sshin	port4 = s.addr[1]
20056668Sshin      else
20156668Sshin	dest4 = "127.0.0.1"
20256668Sshin	port4 = "telnet"
20356668Sshin      end
20456668Sshin      if DEBUG
20556668Sshin	STDERR.print "IPv6 dest: #{dest6}  IPv4 dest: #{dest4}\n" if DEBUG
20656668Sshin      end
20756668Sshin      STDERR.print "connect to #{dest4} #{port4}\n" if DEBUG
20856668Sshin      s4 = TCPsocket.open(dest4, port4)
20956668Sshin      STDERR.print "connected to #{dest4} #{port4}, #{s4.addr[1]}\n" if DEBUG
21056668Sshin      [0, 1].each do |i|
21156668Sshin	threads[i] = Thread.start do
21256668Sshin	  if (i == 0)
21356668Sshin	    tcpcopy(s6, s4)
21456668Sshin	  else
21556668Sshin	    tcpcopy(s4, s6)
21656668Sshin	  end
21756668Sshin	end
21856668Sshin      end
21956668Sshin      STDERR.print "relay_tcp(#{sock}, #{name}) wait\n" if DEBUG
22056668Sshin      for i in threads
22156668Sshin	STDERR.print "relay_tcp(#{sock}, #{name}) wait #{i}\n" if DEBUG
22256668Sshin	i.join
22356668Sshin	STDERR.print "relay_tcp(#{sock}, #{name}) wait #{i} done\n" if DEBUG
22456668Sshin      end
22556668Sshin      STDERR.print "relay_tcp(#{sock}, #{name}) closing s4\n" if DEBUG
22656668Sshin      s4.close
22756668Sshin      STDERR.print "relay_tcp(#{sock}, #{name}) closing s6\n" if DEBUG
22856668Sshin      s6.close
22956668Sshin      STDERR.print "relay_tcp(#{sock}, #{name}) done\n" if DEBUG
23056668Sshin    end
23156668Sshin  end
23256668Sshin  STDERR.print "relay_tcp(#{sock}, #{name}) finished\n" if DEBUG
23356668Sshinend
23456668Sshin
23556668Sshindef usage()
23656668Sshin  STDERR.print "usage: #{$0} [-f] port...\n"
23756668Sshinend
23856668Sshin
23956668Sshin#------------------------------------------------------------
24056668Sshin
24156668Sshin$mode = "tcp"
24256668Sshin
24356668Sshinwhile ARGV[0] =~ /^-/ do
24456668Sshin  case ARGV[0]
24556668Sshin  when /^-f/
24656668Sshin    $mode = "ftp"
24756668Sshin  else
24856668Sshin    usage()
24956668Sshin    exit 0
25056668Sshin  end
25156668Sshin  ARGV.shift
25256668Sshinend
25356668Sshin
25456668Sshinif ARGV.length == 0
25556668Sshin  usage()
25656668Sshin  exit 1
25756668Sshinend
25856668Sshin
25956668Sshinftpport = Socket.getservbyname("ftp")
26056668Sshin
26156668Sshinres = []
26256668Sshinfor port in ARGV
26356668Sshin  t = Socket.getaddrinfo(nil, port, Socket::PF_INET6, Socket::SOCK_STREAM,
26456668Sshin	nil, Socket::AI_PASSIVE)
26556668Sshin  if (t.size <= 0)
26656668Sshin    STDERR.print "FATAL: getaddrinfo failed (port=#{port})\n"
26756668Sshin    exit 1
26856668Sshin  end
26956668Sshin  res += t
27056668Sshinend
27156668Sshin
27256668Sshinsockpool = []
27356668Sshinnames = []
27456668Sshinlistenthreads = []
27556668Sshin
27656668Sshinres.each do |i|
27756668Sshin  s = TCPserver.new(i[3], i[1])
27856668Sshin  n = Socket.getnameinfo(s.getsockname, Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV).join(" port ")
27956668Sshin  if i[6] == IPPROTO_IPV6
28056668Sshin    s.setsockopt(i[6], IPV6_FAITH, 1)
28156668Sshin  end
28256668Sshin  s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
28356668Sshin  sockpool.push s
28456668Sshin  names.push n
28556668Sshinend
28656668Sshin
28756668Sshinif DEBUG
28856668Sshin  (0 .. sockpool.size - 1).each do |i|
28956668Sshin    STDERR.print "listen[#{i}]: #{sockpool[i]} #{names[i]}\n" if DEBUG
29056668Sshin  end
29156668Sshinend
29256668Sshin
29356668Sshin(0 .. sockpool.size - 1).each do |i|
29456668Sshin  listenthreads[i] = Thread.start do
29556668Sshin    if DEBUG
29656668Sshin      STDERR.print "listen[#{i}]: thread #{Thread.current}\n" if DEBUG
29756668Sshin    end
29856668Sshin    STDERR.print "listen[#{i}]: thread #{Thread.current}\n" if DEBUG
29956668Sshin    case $mode
30056668Sshin    when "tcp"
30156668Sshin      relay_tcp(sockpool[i], names[i])
30256668Sshin    when "ftp"
30356668Sshin      relay_ftp(sockpool[i], names[i])
30456668Sshin    end
30556668Sshin  end
30656668Sshinend
30756668Sshin
30856668Sshinfor i in listenthreads
30956668Sshin  i.join
31056668Sshinend
31156668Sshin
31256668Sshinexit 0
313