#!/usr/pkg/bin/ruby32

#
# dtcpd, Turmpet Dynamic Tunnel Configuration Protocol daemon
#

#
# Copyright (c) 2000-2013 Hajimu UMEMOTO <ume@mahoroba.org>
# All rights reserved.
#
# Copyright (C) 1999 WIDE Project.
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of the project nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Id: dtcps.rb,v 1.3 2000/04/21 14:21:21 jinmei Exp $
# $Mahoroba: src/dtcp/dtcps.rb,v 1.72 2013/06/02 14:05:51 ume Exp $
#

require 'optparse'
require "socket"
require "thread"
require "digest/md5"
require "dbm"
require "etc"
require 'syslog'

AUTHTIMEOUT = 60
TUNTIMEOUT = 300
IF_MAXUNIT = 0x7fff
# must be less than 10, against RIPng and PIM6 - useless
TRAFFICTIMEOUT = 0
POPAUTHUID = 'pop'
ROUTETABLE = '/usr/pkg/etc/routetable'
PIDFILE = '/var/run/dtcps.pid'
UDP_TUNNEL_PORT = 4028
IPPORT_MAX = 65535

ROUTE_GATEWAY = 0
ROUTE_CHANGE = 1
ROUTE_INTERFACE = 2
ROUTE_IFP = 3

# FreeBSD port of qpopper 4.X
#POPAUTHDB = '/usr/local/etc/qpopper/pop.auth'
# FreeBSD port of qpopper 2.X
#POPAUTHDB = '/usr/local/etc/popper/pop.auth'
# NetBSD pkg of qpopper 4.X
#POPAUTHDB = '/usr/pkg/etc/apop.auth'
#
POPAUTHDB = '/usr/pkg/etc/apop.auth'

# NetBSD 1.5.x or earlier
# 	and OpenBSD
#TUNIF = "gif[0-9]+"
#TUNIF_CLONING = false
#TUNNEL_CREATE = 'ifconfig %s tunnel %s %s'
#TUNNEL_DELETE = 'ifconfig %s deletetunnel'
#ROUTE_METHOD = ROUTE_CHANGE
#
# FreeBSD 4.6-RELEASE or later
# 	and NetBSD 1.6
# (We don't support gif cloning, yet. You must create gifs before)
#TUNIF = "gif0"
#TUNIF_CLONING = true
#TUNNEL_CREATE = 'ifconfig %s tunnel %s %s'
#TUNNEL_DELETE = 'ifconfig %s deletetunnel'
#ROUTE_METHOD = ROUTE_IFP
#
# FreeBSD 4.4-RELEASE and 4.5-RELEASE
# (We don't support gif cloning, yet. You must create gifs before)
#TUNIF = "gif0"
#TUNIF_CLONING = true
#TUNNEL_CREATE = 'ifconfig %s tunnel %s %s'
#TUNNEL_DELETE = 'ifconfig %s deletetunnel'
#ROUTE_METHOD = ROUTE_GATEWAY
#
# FreeBSD 4.3-RELASE or earlyer
#TUNIF = "gif[0-9]+"
#TUNIF_CLONING = false
#TUNNEL_CREATE = 'gifconfig %s %s %s'
#TUNNEL_DELETE = 'gifconfig %s delete'
#ROUTE_METHOD = ROUTE_GATEWAY
#
TUNIF = 'gif0'
TUNIF_CLONING = true
TUNNEL_CREATE = 'ifconfig %s tunnel %s %s'
TUNNEL_DELETE = 'ifconfig %s deletetunnel'
ROUTE_METHOD = ROUTE_IFP

class TunnelInfo

  attr :thread
  attr :tun

  private

  def initialize(tun)
    @thread = Thread.current
    @tun = tun
  end

end

def daemon(nochdir, noclose)
  pid = fork
  if pid == -1
    return -1
  elsif pid != nil
    exit 0
  end

  Process.setsid()

  Dir.chdir('/') if (nochdir == 0)
  if noclose == 0
    devnull = open("/dev/null", "r+")
    $stdin.reopen(devnull)
    $stdout.reopen(devnull)
    $stderr.reopen(devnull)
  end
  return 0
end

def logmsg(msg)
  if $syslog.opened?
    $syslog.notice('%s', msg)
  else
    $stderr.print msg
  end
end

def debugmsg(msg)
  logmsg(msg) if ($debug)
end

def execute(cmd)
  debugmsg("#{cmd}\n")
  system(cmd)
end

class Interface

  attr :name

  def up
    execute("ifconfig #{@name} up")
  end

  def down
    execute("ifconfig #{@name} down")
  end

  def addaddr(addr)
    execute("ifconfig #{@name} inet6 #{addr} alias")
  end

  def deladdr(addr)
    execute("ifconfig #{@name} inet6 #{addr} -alias")
  end

  def setmtu(mtu = 1500)
    execute("ifconfig #{@name} mtu #{mtu}")
  end

  def linklocal
    `ifconfig #{@name} inet6`.each_line { |s|
      if s =~ /inet6 (fe80::[^ ]*)/
	return $1
      end
    }
    return nil
  end

  def hostid
    laddr = linklocal
    if !laddr
      return nil
    end
    return laddr.sub(/^fe80::/, '').sub(/%.*$/, '')
  end

  private

  def initialize(name)
    @name = name
  end

end

class ClonedInterface < Interface

  attr :created

  def create
    @name = @tunif
    if @cloning
      cmd = sprintf("ifconfig %s create", @name)
      debugmsg("#{cmd}\n")
      `#{cmd}`.each_line { |l|
	if l =~ /^(#{@name}[0-9]+)/
	  @name = $1
	  break
	end
      }
    end
    @created = true
  end

  def created?
    return @created
  end

  def delete
    if @cloning && !@create_only
      execute(sprintf("ifconfig %s destroy", @name))
    end
    @created = false
  end

  private

  def initialize(tunif, cloning, create_only)
    @tunif = tunif
    @cloning = cloning
    @create_only = create_only
    @created = false
  end

end

class GenericTunnel < ClonedInterface

  def create(me = nil, her = nil)
    super()
    if me && her
      tunnel(me, her)
    end
  end

  def delete
    down
    execute(sprintf(TUNNEL_DELETE, @name))
    super
  end

  def tunnel(me, her)
    execute(sprintf(TUNNEL_CREATE, @name, me, her))
    up
  end

  def addpeer(me, her)
    execute("ifconfig #{@name} inet6 #{me} #{her} prefixlen 128 alias")
  end

  def delpeer(me, her)
    execute("ifconfig #{@name} inet6 #{me} #{her} prefixlen 128 -alias")
  end

  def setmtu(mtu = 1280)
    super(mtu)
  end

end

class NetgraphInterface < Interface

  def create
    if !@tunif || @tunif == "ng"
      @name = mkpeer
      @created = true
      return
    end

    if @tunif !~ /^ng([0-9]+)$/
      raise "#{@tunif}: wrong name"
    end
    unitmax = $1.to_i

    shutdown(@tunif)

    bogus = Array.new
    while TRUE
      @name = mkpeer
      if @name == @tunif
	@created = true
	break
      end

      bogus.push(@name)

      if @name !~ /^ng([0-9]+)$/
	raise "#{@name}: wrong name"
      end
      unit = $1.to_i
      if unit > unitmax
	raise "#{@name}: not expected"
      end
    end

    bogus.each { |iface|
      shutdown(iface)
    }
  end

  def created?
    return @created
  end

  def delete
    shutdown(@name)
    @created = false
  end

  private

  def initialize(tunif = nil)
    @tunif = tunif
    @created = false
  end

  def mkpeer()
    iface = nil
    f = IO.popen("ngctl -f - 2> /dev/null", "w+")
    f.write("mkpeer iface dummy inet6\n")
    f.write("msg dummy nodeinfo\n")
    f.write("quit\n")
    f.flush
    f.each_line { |line|
      if line =~ / name="(ng[0-9]+)"/
	iface = $1
	break
      end
    }
    f.close
    if !iface
      raise "ngctl failed"
    end
    return iface
  end

  def shutdown(iface = nil)
    if !iface
      iface = @name
    end
    execute("ngctl shutdown #{iface}: >/dev/null 2>&1")
  end

end

class UDPTunnel < NetgraphInterface

  attr :me_port
  attr :her_port

  def create(me = nil, her = nil)
    super()
    if me && her
      tunnel(me, her)
    end
  end

  def delete
    down
    super
    if @me_port < $udp_tunnel_port
      $udp_tunnel_port = @me_port
    end
  end

  def tunnel(me_addr, her_addr, her_port)
    if !ksocket
      raise "ngctl: mkpeer ksocket fail"
    end

    @me_port = -1
    $udp_tunnel_port.upto(IPPORT_MAX) do |port|
      if bind(me_addr, port)
	@me_port = port
	$udp_tunnel_port = port + 1
	break
      end
    end
    if @me_port < 0
      raise "ngctl: bind fail addr=#{me_addr} port=#{$udp_tunnel_port}"
    end

    if !connect(her_addr, her_port)
      raise "ngctl: connect fail addr=#{her_addr} port=#{her_port}"
    end
    @her_port = her_port
    up
  end

  def addpeer(me, her)
    execute("ifconfig #{@name} inet6 #{me} #{her} prefixlen 128 alias")
  end

  def delpeer(me, her)
    execute("ifconfig #{@name} inet6 #{me} #{her} prefixlen 128 -alias")
  end

  private

  def ksocket()
    execute("ngctl mkpeer #{@name}: ksocket inet6 inet/dgram/udp")
  end

  def bind(addr, port)
    execute("ngctl msg #{@name}:inet6 bind inet/#{addr}:#{port} >/dev/null 2>&1")
  end

  def connect(addr, port)
    execute("ngctl msg #{@name}:inet6 connect inet/#{addr}:#{port}")
  end

end

class InterfaceInfo

  attr :tunif
  attr_accessor :status

  private

  def initialize(tunif, status)
    @tunif = tunif
    @status = status
  end

end

class InterfacePool

  INTERFACE_EXIST = 1
  INTERFACE_INUSE = 2

  def assign(ifname = nil)
    if ifname
      if ifname !~ /^([a-zA-Z]+)(\d+)$/
	return nil
      end
      if_name = $1
      unit = $2.to_i
      if @cloning
	if if_name == @if_name && (@unit_low < 0 || unit >= @unit_low)
	  return nil
	end
      else
	if ifname =~ /^#{@tunif}$/
	  return nil
	end
      end
      tunif = GenericTunnel.new(ifname, @cloning, @create_only)
      tunif.create
      return tunif
    end
    if @cloning
      if @unit_min >= 0
	@unit_min.upto(IF_MAXUNIT) do |unit|
	  s = "#{@if_name}#{unit}"
	  if @interface.has_key?(unit)
	    next if !@create_only
	    next if @interface[unit].status == INTERFACE_INUSE
	    tunif = @interface[unit].tunif
	    @interface[unit].status = INTERFACE_INUSE
	  else
	    tunif = GenericTunnel.new(s, @cloning, @create_only)
	    tunif.create
	    if !@create_only
	      # XXX: Creation failure should be checked actually.
	      next if !tunif.created?
	    end
	    @interface[unit] = InterfaceInfo.new(tunif, INTERFACE_INUSE)
	  end
	  @unit_min = unit + 1
	  break
	end
      else
	tunif = GenericTunnel.new(@ifname, @cloning, @create_only)
	tunif.create
      end
    else
      `ifconfig -a`.each_line { |s|
	next if s !~ /^#{@ifname}:/ || s =~ /UP/o
	ifname = s[0, s.index(':')]
	tunif = GenericTunnel.new(ifname, @cloning, @create_only)
	tunif.create
	break
      }
    end
    return tunif
  end

  def resign(tunif)
    tunif.name =~ /^([a-zA-Z]+)(\d+)$/
    if_name = $1
    unit = $2.to_i

    tunif.delete

    return unless @cloning

    # XXX: Is @create_only check needed?
    if if_name != @if_name && !@create_only
      return
    end

    if @interface.has_key?(unit)
      if @create_only
	@interface[unit].status = INTERFACE_EXIST
      else
	@interface.delete(unit)
      end
    end
    if @unit_low >= 0 && unit >= @unit_low && unit < @unit_min
      @unit_min = unit
    end
  end

  private

  def initialize(ifname, cloning, create_only)
    @ifname = ifname
    @cloning = cloning
    @create_only = create_only

    if @ifname =~ /^([a-zA-Z]+)(\d+)$/
      @if_name = $1
      @unit_low = $2.to_i
      @unit_min = $2.to_i
    elsif @ifname =~ /^([a-zA-Z]+)$/
      @if_name = $1
      @unit_low =  -1
      @unit_min =  -1
      @create_only = false
    else
      @if_name = ""
      @unit_low =  -1
      @unit_min =  -1
      @cloning = false
    end

    @interface = Hash.new
  end

end

class NetgraphInterfacePool

  def assign(ifname = nil)
    if ifname
      if ifname !~ /^(ng)(\d+)$/
	return nil
      end
      if_name = $1
      unit = $2.to_i
      if if_name == @if_name && (@unit_low < 0 || unit >= @unit_low)
	return nil
      end
      tunif = UDPTunnel.new(ifname)
      tunif.create
      return tunif
    end
    if @unit_min >= 0
      tunif = UDPTunnel.new("#{@if_name}#{@unit_min}")
      tunif.create
      if tunif.name !~ /^(ng)(\d+)$/
	return nil
      end
      @unit_min = $2.to_i
    else
      tunif = UDPTunnel.new(@ifname)
      tunif.create
    end
    return tunif
  end

  def resign(tunif)
    if tunif.name !~ /^(ng)(\d+)$/
      return
    end
    if_name = $1
    unit = $2.to_i

    tunif.delete

    if if_name != @if_name
      return
    end

    if @unit_low >= 0 && unit >= @unit_low && unit < @unit_min
      @unit_min = unit
    end
  end

  private

  def initialize(ifname)
    @ifname = ifname

    if @ifname =~ /^(ng)(\d+)$/
      @if_name = $1
      @unit_low = $2.to_i
      @unit_min = $2.to_i
    elsif @ifname =~ /^(ng)$/
      @if_name = $1
      @unit_low =  -1
      @unit_min =  -1
    else
      raise "#{@ifname}: invalid interface name"
    end
  end

end

def route_add(dest, tunif)
  case ROUTE_METHOD
  when ROUTE_CHANGE
    cmd = "route add -inet6 #{dest} ::1; route change -inet6 #{dest} -ifp #{tunif}"
  when ROUTE_INTERFACE
    cmd = "route add -inet6 #{dest} -interface #{tunif}"
  when ROUTE_IFP
    cmd = "route add -inet6 #{dest} ::1 -ifp #{tunif}"
  else
    laddr = getladdr(tunif)
    if !laddr
      return nil
    end
    cmd = "route add -inet6 #{dest} #{laddr}"
  end
  return cmd
end

def getladdr(tunif)
  `ifconfig #{tunif} inet6`.each_line { |s|
    if s =~ /inet6 (fe80::[^ ]*)/
      return $1
    end
  }
  return nil
end

def getprefix(user)
  prefixes = nil
  tunif = nil
  begin
    open(ROUTETABLE) do |f|
      f.each_line do |l|
	l.chop!.sub!('\s*#.*$', '')
	next if l =~ /^\s*$/o
	if l =~ /^#{user}\s+/
	  t = l.split(/\s+/)
	  (1 .. t.size - 1).each { |i|
	    t[i] = nil if t[i] == "-"
	  }
	  prefixes = t[1]
	  tunif = t[2]
	  break
	end
      end
    end
  rescue
    debugmsg("routetable is not found\n")
    return nil, nil
  end
  return prefixes, tunif
end

def routesetup(tunif, prefixes)
  prefixes.split(/\s*,\s*/).each { |prefix|
    heraddr, herlen = prefix.split('/')
    cmd = route_add("#{heraddr} -prefixlen #{herlen}", tunif)
    if !cmd
      debugmsg("#{user}: cannot get link-local address of #{tunif}\n")
      return false
    end
    execute(cmd)
  }
  return true
end

class Tunnel

  attr_accessor :tunif
  attr_accessor :info

  def delete
    logmsg("#{@tunif.name} disconnected\n")
    case @info.length
    when 3				# network type tunnel
      @info[2].split(/\s*,\s*/).each { |her_prefix|
	heraddr, herlen = her_prefix.split('/')
	execute("route delete -inet6 #{heraddr} -prefixlen #{herlen}")
      }
    when 4				# host type tunnel
      delpeer(@tunif, @info[3], @info[2])
    when 5				# network type tunnel
      @info[4].split(/\s*,\s*/).each { |her_prefix|
	heraddr, herlen = her_prefix.split('/')
	execute("route delete -inet6 #{heraddr} -prefixlen #{herlen}")
      }
      delpeer(@tunif, @info[3], @info[2])
    end
    _delete(@tunif)
    @tunif = nil
  end

  private

  def initialize(s, user, type, mtu = 0, her_port = -1)
    me = s.addr()[3]
    her = s.peeraddr()[3]
    debugmsg("#{s}: tunnel #{me} -> #{her}\n")

    if $prefix == nil
      case type
      when 'host'
	debugmsg("#{s}: tunnel type #{type} not supported\n")
	raise "unsupported tunnel type #{type}"
      when 'network'
	if $network_with_peeraddr
	  debugmsg("#{s}: internal error: tunnel configuration is wrong\n")
	  raise 'internal error: tunnel configuration is wrong'
	end
      end
    end

    case type
    when 'network'
      her_prefixes, ifname = getprefix(user)
      if !her_prefixes
	logmsg("#{user}: not registered to use network tunnel type\n")
	raise 'prefix is not assigned'
      end
    when 'tunnelonly', 'host'
      her_prefixes, ifname = getprefix(user)
    else
      debugmsg("#{s}: unsupported tunnel type #{type}\n")
      raise "unsupported tunnel type #{type}"
    end

    @mtu = mtu
    tunif = create(me, her, ifname, her_port)
    if tunif == nil
      debugmsg("#{s}: tunnel interface sold out\n")
      raise 'tunnel interface sold out'
    end
    debugmsg("#{s}: tunnel interface #{tunif.name}\n")

    myaddr = nil
    if type == 'host' || (type == 'network' && $network_with_peeraddr)
      myaddr, heraddr = addpeer(tunif, $prefix)
      if !myaddr || !heraddr
	_delete(tunif)
	raise 'internal error: tunnel interface name format is wrong'
      end
    end

    if type == 'network'
      if !routesetup(tunif.name, her_prefixes)
	if myaddr
	  delpeer(tunif, myaddr, heraddr)
	end
	_delete(tunif)
	raise 'prefix is not assigned'
      end
    end

    @tunif = tunif
    me = $global ? $global : me
    if tunif.instance_of?(UDPTunnel)
      me.concat(";#{tunif.me_port}")
    end
    case type
    when 'tunnelonly'
      @info = [her, me]
    when 'host'
      @info = [her, me, heraddr, myaddr]
    when 'network'
      if $network_with_peeraddr
	@info = [her, me, heraddr, myaddr, her_prefixes]
      else
	@info = [her, me, her_prefixes]
      end
    else
      debugmsg("#{s}: unsupported tunnel type #{type}\n")
      raise "unsupported tunnel type #{type}"
    end
  end

  def create(me, her, ifname, her_port)
    if her_port >= 0
      if ifname !~ /^ng(\d+)$/
	ifname = nil
      end
      tunif = $ng_interface.assign(ifname)
    else
      if ifname !~ /^gif(\d+)$/
	ifname = nil
      end
      tunif = $interface.assign(ifname)
    end
    if tunif
      if tunif.instance_of?(UDPTunnel)
	tunif.tunnel(me, her, her_port)
      else
	tunif.tunnel(me, her)
      end
      if @mtu > 0
	tunif.setmtu(@mtu)
      end
    end
    return tunif
  end

  def _delete(tunif)
    if @mtu > 0
      tunif.setmtu
    end
    if tunif.instance_of?(UDPTunnel)
      $ng_interface.resign(tunif)
    else
      $interface.resign(tunif)
    end
  end

  def addpeer(tunif, prefix)
    if tunif.name !~ /(\d+)$/
      return nil, nil
    end
    tunid = $1.to_i
    heraddr = sprintf("%s%04x", prefix, (tunid + 1) * 4 + 2)
    myaddr = sprintf("%s%04x", prefix, (tunid + 1) * 4 + 1)
    tunif.addpeer(myaddr, heraddr)
    return myaddr, heraddr
  end

  def delpeer(tunif, me, her)
    tunif.delpeer(me, her)
  end

end

def getipkts(intface)
  tmpfile = "/tmp/getipkts#{$$}.#{intface}"
  system("netstat -in -I #{intface} > #{tmpfile}")
  f = open(tmpfile, "r")
  s = f.readline
  s = f.readline
  f.close
  File::unlink(tmpfile)
  t = s.split(/[ \t]+/)
  if t.length < 5
    debugmsg("#{intface} ipkts unknown, returning -1\n")
  end
  debugmsg("#{intface} ipkts = #{t[t.length - 5]}\n")
  return t[t.length - 5]
end

def checktraffic(tun)
  return if TRAFFICTIMEOUT == 0
  ipkts = getipkts(tun.name)
  while TRUE
    sleep TRAFFICTIMEOUT
    i = getipkts(tun.name)
    next if i == -1
    break if ipkts >= i
    ipkts = i
  end
end

def sendmsg(sock, msg)
  debugmsg("#{sock}: sent <#{msg}>\n")
  sock.print "#{msg}\r\n"
end

def service_dtcp(sock, name)
  debugmsg("service_dtcp(#{sock}, #{name})\n")
  while TRUE
    debugmsg("service_dtcp(#{sock}, #{name}) accepting\n")
    Thread.start(sock.accept) { |s|
      debugmsg("service_dtcp(#{sock}, #{name}) accepted #{s}\n")
      tun = nil
      user = nil

      # send challenge
      challenge = seed()
      s.print "+OK #{challenge} KAME tunnel server\r\n"

      # check response
      # tunnel itojun RESPONSE type
      while TRUE
	t = IO.select([s], [], [s], tun == nil ? AUTHTIMEOUT : TUNTIMEOUT)
	if t == nil
	  s.print "-ERR connection timed out, disconnecting\r\n"
	  break
	end
	if s.eof?
	  break
	end
	if user
	  # be careful.  it may accesses non existence member wrongly.
	  # so, make sure to copy context, 1st.
	  alive = $tunnel[user]
	  if !alive
	    debugmsg("#{user} was disconnected\n")
	    break
	  end
	  if alive.thread != Thread.current
	    debugmsg("#{user} has another new session\n")
	    break
	  end
	end
	response = s.readline
	response.gsub!(/[\n\r]/, '')
	if response != ''
	  t = response.split(/ /)
	  t[0].tr!('A-Z', 'a-z')
	else
	  t = ['']
	end
	debugmsg("#{s}: got <#{response}>\n")
	case t[0]
	when 'tunnel'
	  mtu = 0
	  her_port = 0
	  udp_tunnel = false
	  if t.length >= 5
	    opterr = false
	    4.upto(t.length - 1) do |opt|
	      if t[opt] =~ /^[0-9]+$/
		# backward compatibility
		mtu = t[opt].to_i
	      else
		var, val = t[opt].split(/=/)
		case var
		when 'mtu'
		  if val !~ /^[0-9]+$/
		    opterr = true
		    break
		  end
		  mtu = val.to_i
		when 'port'
		  if val !~ /^[0-9]+$/
		    opterr = true
		    break
		  end
		  her_port = val.to_i
		when 'proto'
		  case val
		  when 'udp'
		    if !$udp_tunnel
		      opterr = true
		      break
		    end
		    udp_tunnel = true
		  else
		    opterr = true
		    break
		  end
		else
		  opterr = true
		  break
		end
	      end
	    end
	    if opterr
	      logmsg("client #{s} sent wrong #{t[0]} command\n")
	      sendmsg(s, "-ERR authentication failed.")
	      next
	    end
	  end
	  if !udp_tunnel
	    her_port = -1
	  end

	  user = t[1]
	  type = (t[3] == 'tunnelroute') ? 'network' : t[3]
	  pass = getpopauth(user)
	  if pass == nil
	    logmsg("client #{s} has no password in database for #{user}\n")
	    sendmsg(s, "-ERR authentication failed.")
	    next
	  end
	  # get password from the username
#	  $stderr.print "authenticate(#{user} #{challenge} #{pass}): "
#	  debugmsg(authenticate(user, challenge, pass) + "\n")
#	  debugmsg("target: #{t[2]}\n")
 	  if (authenticate(user, challenge, pass) == t[2])
	    debugmsg("client #{s.peeraddr()[3]} on #{s}\n")
	    logmsg("client #{s.peeraddr()[3]} authenticated as #{user}\n")
	    auth = true
	    err = ''
	    $mutex.synchronize {
	      if $tunnel.has_key?(user)
		logmsg("#{user}: duplicate login was detected\n")
		$tunnel[user].tun.delete
		$tunnel.delete(user)
	      end
	      her = s.peeraddr()[3]
	      if !udp_tunnel
		$tunnel.each { |u, t|
		  if t.tun.tunif.instance_of?(GenericTunnel) &&
		     t.tun.info[0] == her
		    logmsg("#{user}: her IPv4 address #{her} was conflicted with #{u}\n")
		    t.tun.delete
		    $tunnel.delete(u)
		    break
		  end
		}
	      end
	      begin
		tun = Tunnel.new(s, user, type, mtu, her_port)
	      rescue => e
		err = e.to_str
	      end
	      if tun != nil
		$tunnel[user] = TunnelInfo.new(tun)
	      end
	    }
	    if tun == nil
	      logmsg("failed to configure for #{user} type #{type}: #{err}\n")
	      sendmsg(s, "-ERR #{err}")
	    else
	      t = tun.info.join(' ')
	      logmsg("#{tun.tunif.name} configured for #{user} type #{type}: #{t}\n")
	      sendmsg(s, "+OK #{t}")
	    end
	  else
	    logmsg("client #{s} not authenticated\n")
	    sendmsg(s, "-ERR authentication failed.")
	  end
	when 'ping'
	  sendmsg(s, "+OK hi, happy to hear from you")
	when 'help'
	  sendmsg(s, "+OK valid commands are: TUNNEL PING QUIT")
	when 'quit'
	  sendmsg(s, "+OK see you soon.")
	  break
	else
	  debugmsg("client #{s} sent invalid command #{t[0]}\n")
	  sendmsg(s, "-ERR invalid command")
	end
      end
      if tun != nil
	$mutex.synchronize {
	  if $tunnel.has_key?(user) && $tunnel[user].thread == Thread.current
	    checktraffic(tun)
	    tun.delete
	    $tunnel.delete(user)
	  end
	}
      end
      begin
	s.flush
      rescue
      end
      begin
	s.shutdown(1)
      rescue
      end
      begin
	s.close
      rescue
      end
      debugmsg("shutdown #{s} #{Thread.current}\n")
    }
  end
  debugmsg("service_dtcp(#{sock}, #{name}) finished\n")
end

def usage()
  $stderr.print "usage: #{File.basename($0)} [-dDU] [-b udp-port] [-i interfaces] [-I udp-interface] [-p port] [prefix]\n"
end

def seed()
  m = Digest::MD5.new
  m.update(Time.now.to_s)
  m.update($$.to_s)
  m.update(Socket.gethostname())
  return m.digest.unpack("H32")[0].tr('a-f', 'A-F')
end

def authenticate(user, seed, pass)
  m = Digest::MD5.new
  m.update(user)
  m.update(seed)
  m.update(pass)
  return m.digest.unpack("H32")[0].tr('a-f', 'A-F')
end

# NOTE: strings are terminated by "\0"...
def getpopauth(user)
  pw = Etc.getpwnam(POPAUTHUID)
  if pw == nil
    debugmsg("no user named pop\n")
    return nil
  end
  origuid = Process.euid
  # XXX begin seteuid(pop)
  Process.euid = pw[2]
  f = DBM.open(POPAUTHDB, nil)
  if f == nil
    debugmsg("no password database found\n")
    Process.euid = origuid
    return nil
  end
  p = f[user + "\0"]
  f.close
  Process.euid = origuid
  # XXX end seteuid(pop)
  if p == nil
    debugmsg("no relevant password database item found\n")
    return nil
  end
  p.sub!(/\0+$/, '')
  q = []
  p.each_byte { |byte|
    q.push(byte ^ 0xff)
  }
  p = q.pack('C*')
  debugmsg("ok, relevant password database item found\n")
  return p
end

def terminate
  debugmsg("signal was received\n")
  $mutex.synchronize {
    $tunnel.each_key { |user|
      $tunnel[user].tun.delete
      $tunnel.delete(user)
    }
  }
  # for safety
  if !$cloning
    `ifconfig -lu`.chop.split(/ +/o).grep(/^#{$tunif}$/).each { |i|
      execute("ifconfig #{i} down")
      execute(sprintf(TUNNEL_DELETE, i))
    }
  end
  if $daemonize
    File.unlink(PIDFILE)
  end
  exit 0
end

#------------------------------------------------------------

port = 20200
$tunif = TUNIF
$ng_tunif = "ng"
$cloning = TUNIF_CLONING
$global = nil
$prefix = nil
$network_with_peeraddr = nil
$udp_tunnel_port = UDP_TUNNEL_PORT

begin
  params = ARGV.getopts('ab:cdDg:i:I:op:U')
rescue
  usage()
  exit 0
end

$network_with_peeraddr = params["a"] if params["a"]
$udp_tunnel_port = params["b"].to_i if params["b"]
$cloning = false if params["c"]
$debug = params["d"]
$daemonize = !params["D"]
$global = params["g"] if params["g"]
$tunif = params["i"] if params["i"]
$ng_tunif = params["I"] if params["I"]
$create_only = params["o"]
port = params["p"].to_i if params["p"]
$udp_tunnel = params["U"]

case ARGV.length
when 0
  $prefix = nil
when 1
  $prefix = ARGV[0]
  if $prefix !~ /^[0-9a-fA-f:]*::$/
    usage()
    exit 1
  end
else
  usage()
  exit 1
end

res = []
t = Socket.getaddrinfo(nil, port, Socket::PF_INET, Socket::SOCK_STREAM,
      nil, Socket::AI_PASSIVE)
if (t.size <= 0)
  logmsg("FATAL: getaddrinfo failed (port=#{port})\n")
  exit 1
end
res += t

$syslog = Syslog.instance
if $daemonize
  daemon(0, 0)
  $syslog.open(File.basename($0), Syslog::LOG_PID, Syslog::LOG_DAEMON)
  open(PIDFILE, "w") { |pid|
    pid.print "#{$$}\n"
  }
end

$mutex = Mutex.new
$tunnel = Hash.new
$interface = InterfacePool.new($tunif, $cloning, $create_only)
if $udp_tunnel
  $ng_interface = NetgraphInterfacePool.new($ng_tunif)
end

sockpool = []
names = []
listenthreads = []

res.each do |i|
  s = TCPServer.new(i[3], i[1])
  n = Socket.getnameinfo(s.getsockname, Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV).join(" port ")
  s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
  sockpool.push s
  names.push n
end

if $debug
  (0 .. sockpool.size - 1).each do |i|
    debugmsg("listen[#{i}]: #{sockpool[i]} #{names[i]}\n")
  end
end

trap("SIGTERM") { terminate }
trap("SIGINT") { terminate }
trap("SIGHUP", "SIG_IGN")

(0 .. sockpool.size - 1).each do |i|
  listenthreads[i] = Thread.start {
    debugmsg("listen[#{i}]: thread #{Thread.current}\n")
    service_dtcp(sockpool[i], names[i])
  }
end

for i in listenthreads
  i.join
end

if $daemonize
  File.unlink(PIDFILE)
end
exit 0
