#!@RUBY@
#
# dtcpd, Turmpet Dynamic Tunnel Configuration Protocol daemon
#
#
# Copyright (c) 2000-2006 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.70 2006/01/08 18:24:02 ume Exp $
#
require 'getopts'
require "socket"
require "thread"
require "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 = '@PREFIX@/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 = '@POPAUTHDB@'
# 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 = '@DTCPS_TUNIF@'
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 { |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 { |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 { |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 { |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 = 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 = MD5.new(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 = MD5.new(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
while p.length > 0 && p[p.length - 1] == 0
p = p[0, p.length - 1]
end
for i in 0 .. p.length - 1
p[i] = [p[i] ^ 0xff].pack('C')
end
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
if !getopts('acdDoU', 'b:', 'g:', 'i:', 'I:', 'p:')
usage()
exit 0
end
$network_with_peeraddr = $OPT_a if $OPT_a
$udp_tunnel_port = $OPT_b if $OPT_b
$cloning = false if $OPT_c
$debug = $OPT_d
$daemonize = !$OPT_D
$global = $OPT_g if $OPT_g
$tunif = $OPT_i if $OPT_i
$ng_tunif = $OPT_I if $OPT_I
$create_only = $OPT_o
port = $OPT_p if $OPT_p
$udp_tunnel = $OPT_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
if VERSION =~ /^1\.2/
Thread.join(i)
else
i.join
end
end
if $daemonize
File.unlink(PIDFILE)
end
exit 0
syntax highlighted by Code2HTML, v. 0.9.1