1# faithd, ruby version.  requires v6-enabled ruby.
2#
3# highly experimental (not working right at all) and very limited
4# functionality.
5#
6# $Id: faithd.rb,v 1.1.2.4 1999/05/10 17:06:30 itojun Exp $
7# $FreeBSD$
8
9require "socket"
10require "thread"
11
12# XXX should be derived from system headers
13IPPROTO_IPV6 = 41
14IPV6_FAITH = 29
15DEBUG = true
16DEBUG_LOOPBACK = true
17
18# TODO: OOB data handling
19def tcpcopy(s1, s2, m)
20  STDERR.print "tcpcopy #{s1} #{s2}\n" if DEBUG
21  buf = ""
22  while TRUE
23    begin
24      buf = s1.sysread(100)
25      s2.syswrite(buf)
26    rescue EOFError
27      break
28    rescue IOError
29      break
30    end
31  end
32  STDERR.print "tcpcopy #{s1} #{s2} finished\n" if DEBUG
33  s1.shutdown(0)
34  s2.shutdown(1)
35end
36
37def relay_ftp_passiveconn(s6, s4, dport6, dport4)
38  Thread.start do
39    d6 = TCPserver.open("::", dport6).accept
40    d4 = TCPsocket.open(s4.getpeer[3], dport4)
41    t = []
42    t[0] = Thread.start do
43      tcpcopy(d6, d4)
44    end
45    t[1] = Thread.start do
46      tcpcopy(d4, d6)
47    end
48    for i in t
49      i.join
50    end
51    d4.close
52    d6.close
53  end
54end
55
56def ftp_parse_2428(line)
57  if (line[0] != line[line.length - 1])
58    return nil
59  end
60  t = line.split(line[0 .. 0])	# as string
61  if (t.size != 4 || t[1] !~ /^[12]$/ || t[3] !~ /^\d+$/)
62    return nil
63  end
64  return t[1 .. 3]
65end
66
67def relay_ftp_command(s6, s4, state)
68  STDERR.print "relay_ftp_command start\n" if DEBUG
69  while TRUE
70    begin
71      STDERR.print "s6.gets\n" if DEBUG
72      line = s6.gets
73      STDERR.print "line is #{line}\n" if DEBUG
74      if line == nil
75	return nil
76      end
77
78      # translate then copy
79      STDERR.print "line is #{line}\n" if DEBUG
80      if (line =~ /^EPSV\r\n/i)
81	STDERR.print "EPSV -> PASV\n" if DEBUG
82	line = "PASV\n"
83	state = "EPSV"
84      elsif (line =~ /^EPRT\s+(.+)\r\n/i)
85	t = ftp_parse_2428($1)
86	if t == nil
87	  s6.puts "501 illegal parameter to EPRT\r\n"
88	  next
89	end
90
91	# some tricks should be here
92	s6.puts "501 illegal parameter to EPRT\r\n"
93	next
94      end
95      STDERR.print "fail: send #{line} as is\n" if DEBUG
96      s4.puts(line)
97      break
98    rescue EOFError
99      return nil
100    rescue IOError
101      return nil
102    end
103  end
104  STDERR.print "relay_ftp_command finish\n" if DEBUG
105  return state
106end
107
108def relay_ftp_status(s4, s6, state)
109  STDERR.print "relay_ftp_status start\n" if DEBUG
110  while TRUE
111    begin
112      line = s4.gets
113      if line == nil
114	return nil
115      end
116
117      # translate then copy
118      s6.puts(line)
119
120      next if line =~ /^\d\d\d-/
121      next if line !~ /^\d/
122
123      # special post-processing
124      case line
125      when /^221 /	# result to QUIT
126	s4.shutdown(0)
127	s6.shutdown(1)
128      end
129
130      break if (line =~ /^\d\d\d /)
131    rescue EOFError
132      return nil
133    rescue IOError
134      return nil
135    end
136  end
137  STDERR.print "relay_ftp_status finish\n" if DEBUG
138  return state
139end
140
141def relay_ftp(sock, name)
142  STDERR.print "relay_ftp(#{sock}, #{name})\n" if DEBUG
143  while TRUE
144    STDERR.print "relay_ftp(#{sock}, #{name}) accepting\n" if DEBUG
145    s = sock.accept
146    STDERR.print "relay_ftp(#{sock}, #{name}) accepted #{s}\n" if DEBUG
147    Thread.start do
148      threads = []
149      STDERR.print "accepted #{s} -> #{Thread.current}\n" if DEBUG
150      s6 = s
151      dest6 = s.addr[3]
152      if !DEBUG_LOOPBACK
153	t = s.getsockname.unpack("x8 x12 C4")
154	dest4 = "#{t[0]}.#{t[1]}.#{t[2]}.#{t[3]}"
155	port4 = s.addr[1]
156      else
157	dest4 = "127.0.0.1"
158	port4 = "ftp"
159      end
160      if DEBUG
161	STDERR.print "IPv6 dest: #{dest6}  IPv4 dest: #{dest4}\n" if DEBUG
162      end
163      STDERR.print "connect to #{dest4} #{port4}\n" if DEBUG
164      s4 = TCPsocket.open(dest4, port4)
165      STDERR.print "connected to #{dest4} #{port4}, #{s4.addr[1]}\n" if DEBUG
166      state = 0
167      while TRUE
168	# translate status line
169	state = relay_ftp_status(s4, s6, state)
170	break if state == nil
171	# translate command line
172	state = relay_ftp_command(s6, s4, state)
173	break if state == nil
174      end
175      STDERR.print "relay_ftp(#{sock}, #{name}) closing s4\n" if DEBUG
176      s4.close
177      STDERR.print "relay_ftp(#{sock}, #{name}) closing s6\n" if DEBUG
178      s6.close
179      STDERR.print "relay_ftp(#{sock}, #{name}) done\n" if DEBUG
180    end
181  end
182  STDERR.print "relay_ftp(#{sock}, #{name}) finished\n" if DEBUG
183end
184
185def relay_tcp(sock, name)
186  STDERR.print "relay_tcp(#{sock}, #{name})\n" if DEBUG
187  while TRUE
188    STDERR.print "relay_tcp(#{sock}, #{name}) accepting\n" if DEBUG
189    s = sock.accept
190    STDERR.print "relay_tcp(#{sock}, #{name}) accepted #{s}\n" if DEBUG
191    Thread.start do
192      threads = []
193      STDERR.print "accepted #{s} -> #{Thread.current}\n" if DEBUG
194      s6 = s
195      dest6 = s.addr[3]
196      if !DEBUG_LOOPBACK
197	t = s.getsockname.unpack("x8 x12 C4")
198	dest4 = "#{t[0]}.#{t[1]}.#{t[2]}.#{t[3]}"
199	port4 = s.addr[1]
200      else
201	dest4 = "127.0.0.1"
202	port4 = "telnet"
203      end
204      if DEBUG
205	STDERR.print "IPv6 dest: #{dest6}  IPv4 dest: #{dest4}\n" if DEBUG
206      end
207      STDERR.print "connect to #{dest4} #{port4}\n" if DEBUG
208      s4 = TCPsocket.open(dest4, port4)
209      STDERR.print "connected to #{dest4} #{port4}, #{s4.addr[1]}\n" if DEBUG
210      [0, 1].each do |i|
211	threads[i] = Thread.start do
212	  if (i == 0)
213	    tcpcopy(s6, s4)
214	  else
215	    tcpcopy(s4, s6)
216	  end
217	end
218      end
219      STDERR.print "relay_tcp(#{sock}, #{name}) wait\n" if DEBUG
220      for i in threads
221	STDERR.print "relay_tcp(#{sock}, #{name}) wait #{i}\n" if DEBUG
222	i.join
223	STDERR.print "relay_tcp(#{sock}, #{name}) wait #{i} done\n" if DEBUG
224      end
225      STDERR.print "relay_tcp(#{sock}, #{name}) closing s4\n" if DEBUG
226      s4.close
227      STDERR.print "relay_tcp(#{sock}, #{name}) closing s6\n" if DEBUG
228      s6.close
229      STDERR.print "relay_tcp(#{sock}, #{name}) done\n" if DEBUG
230    end
231  end
232  STDERR.print "relay_tcp(#{sock}, #{name}) finished\n" if DEBUG
233end
234
235def usage()
236  STDERR.print "usage: #{$0} [-f] port...\n"
237end
238
239#------------------------------------------------------------
240
241$mode = "tcp"
242
243while ARGV[0] =~ /^-/ do
244  case ARGV[0]
245  when /^-f/
246    $mode = "ftp"
247  else
248    usage()
249    exit 0
250  end
251  ARGV.shift
252end
253
254if ARGV.length == 0
255  usage()
256  exit 1
257end
258
259ftpport = Socket.getservbyname("ftp")
260
261res = []
262for port in ARGV
263  t = Socket.getaddrinfo(nil, port, Socket::PF_INET6, Socket::SOCK_STREAM,
264	nil, Socket::AI_PASSIVE)
265  if (t.size <= 0)
266    STDERR.print "FATAL: getaddrinfo failed (port=#{port})\n"
267    exit 1
268  end
269  res += t
270end
271
272sockpool = []
273names = []
274listenthreads = []
275
276res.each do |i|
277  s = TCPserver.new(i[3], i[1])
278  n = Socket.getnameinfo(s.getsockname, Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV).join(" port ")
279  if i[6] == IPPROTO_IPV6
280    s.setsockopt(i[6], IPV6_FAITH, 1)
281  end
282  s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
283  sockpool.push s
284  names.push n
285end
286
287if DEBUG
288  (0 .. sockpool.size - 1).each do |i|
289    STDERR.print "listen[#{i}]: #{sockpool[i]} #{names[i]}\n" if DEBUG
290  end
291end
292
293(0 .. sockpool.size - 1).each do |i|
294  listenthreads[i] = Thread.start do
295    if DEBUG
296      STDERR.print "listen[#{i}]: thread #{Thread.current}\n" if DEBUG
297    end
298    STDERR.print "listen[#{i}]: thread #{Thread.current}\n" if DEBUG
299    case $mode
300    when "tcp"
301      relay_tcp(sockpool[i], names[i])
302    when "ftp"
303      relay_ftp(sockpool[i], names[i])
304    end
305  end
306end
307
308for i in listenthreads
309  i.join
310end
311
312exit 0
313