-------------------------------------------------------------------------------- -- Title: Ident.lua -- Description: Like a square peg in a round hole -- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ -- Creation Date: September 21, 2008 -- Legal: Copyright (C) 2008 Raphaël Szwarc -- Under the terms of the MIT License -- http://www.opensource.org/licenses/mit-license.html -------------------------------------------------------------------------------- -- import dependencies local socket = require( 'socket' ) local assert = assert local getmetatable = getmetatable local ipairs = ipairs local setmetatable = setmetatable local tonumber = tonumber local tostring = tostring -------------------------------------------------------------------------------- -- Ident -------------------------------------------------------------------------------- module( 'Ident' ) _VERSION = '1.0' local self = setmetatable( _M, {} ) local meta = getmetatable( self ) -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- local IPRange = { { 167772160, 184549375 }, -- 10.0.0.0 to 10.255.255.255 { 2130706433, 2130706433 }, -- 127.0.0.1 to 127.0.0.1 { 2886729728, 2887778303 }, -- 172.16.0.0 to 172.31.255.255 { 3232235520, 3232301055 } -- 192.168.0.0 to 192.168.255.255 } local function IPNumber( anAddress ) local anAddress = anAddress or '0.0.0.0' local aNumber = 0 for anOctet in anAddress:gmatch( '%d+' ) do aNumber = ( aNumber * 256 ) + ( tonumber( anOctet ) or 0 ) end return aNumber end local function Accept( anAddress ) local aNumber = IPNumber( anAddress ) for anIndex, aRange in ipairs( IPRange ) do if aNumber >= aRange[ 1 ] and aNumber <= aRange[ 2 ] then return true end end return false end local function Connection( anAddress, aPort ) local aPort = aPort or 113 local aConnection = socket.tcp() if aConnection then aConnection:settimeout( 0.1 ) aConnection:connect( anAddress, aPort ) return aConnection end end local function Ident( aRemoteAddress, aRemotePort, aLocalPort ) local aConnection = Connection( aRemoteAddress ) if aConnection then local aRequest = ( '%d,%d\r\n' ):format( aRemotePort, aLocalPort ) aConnection:send( aRequest ) local aResponse = aConnection:receive( '*l' ) or '' local aServerPort, aClientPort, aType, anOS, anIdent = aResponse:match( '(%d+)%s*,%s*(%d+)%s*:%s*(.-)%s*:%s*(.-)%s*:%s*(.+)' ) aConnection:close() if aServerPort and aClientPort and aType and anOS and anIdent then return anIdent end end end -------------------------------------------------------------------------------- -- Metamethods -------------------------------------------------------------------------------- local cache = setmetatable( {}, { __mode = 'k' } ) function meta:__call( aRemoteAddress, aRemotePort, aLocalPort ) if aRemoteAddress then local aRemoteAddress = socket.dns.toip( aRemoteAddress ) or '0.0.0.0' local aRemotePort = aRemotePort or 0 local aLocalPort = aLocalPort or 0 local aKey = ( '%s:%d,%d' ):format( aRemoteAddress, aRemotePort, aLocalPort ) local anIndent = cache[ aKey ] if anIndent == nil and Accept( aRemoteAddress ) then anIdent = Ident( aRemoteAddress, aRemotePort, aLocalPort ) cache[ aKey ] = anIdent or false end if anIdent then return anIdent end end end function meta:__concat( aValue ) return tostring( self ) .. tostring( aValue ) end function meta:__tostring() return ( '%s/%s' ):format( self._NAME, self._VERSION ) end