#!/usr/local/bin/ruby19
 
# Copyright (c) 2014, Insomnia 24/7
# 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.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
 
# This is an identd proxy for pfsense
# It forwards ident requests to a NAT'ed server
# based on the pf NAT status table and sends
# the response back to the original requester.
 
require 'socket'
 
# Address and port to listen on.
# Normally this is your WAN address.
addr = '80.112.131.85'
port = 113
 
# Create listen server
server = TCPServer.new( addr, port )
 
# Forward request to NAT'ed server
def forwardrequest( host, p1, p2 )
	begin
		# Create new socket to NAT'ed server
		s = TCPSocket.new( host, 113 )
 
		# Send request to NAT'ed server
		s.puts "#{p1}, #{p2}"
 
		return s.gets
	rescue Exception => e
		return nil
	end
end
 
# Look up function for NAT table
def natlookup( p1, p2 )
	# Execute shell command to get NAT table
	nattable = %x(pfctl -ss | egrep '(>.*>|<.*<)')
 
	# Search NAT table for matching enties
	if( nattable =~ /tcp (.+):(.+) -> (.+):#{p1} -> (.+):#{p2} / )
		server = $1
		p1 = $2.to_i
		return [server, p1, p2]
	else
		return nil
	end
end
 
# Main server loop
loop do
	begin
		# New client connected
		client = server.accept
 
		# Read request from socket
		input = client.gets
		input.strip!
 
		puts "Request:  #{input}"
 
		sane = true
		p1 = 0
		p2 = 0
 
		# Check if it's something that looks like an ident response
		if( input =~ /^(\d+)(|\s),(|\s)(\d+)$/ )
			p1 = $1.to_i
			p2 = $4.to_i
		else
			sane = false
		end
 
		# Sanity check on port number
		if( p1 < 1 || p1 > 65535 || p2 < 1 || p2 > 65535 )
			sane = false
		end
 
		# Send generic error for stuff that does not look like ident requests
		if(!sane)
			client.puts "#{input}:ERROR:NO-USER"
			client.close
			next
		end
 
		# Do NAT table lookup
		n = natlookup( p1, p2 )
 
		# Check if there was a NAT table entry
		if( n.nil? )
			client.puts "#{input}:ERROR:NO-USER"
			client.close
			next
		end
 
		# Forward request to NAT'ed server
		result = forwardrequest( n[0], n[1], n[2] )
 
		# Show result to client
		if( result.nil? )
			client.puts "#{input}:ERROR:NO-USER"
			client.close
			next
		end
 
		puts "Response: #{result}"
		client.puts result
 
		# Close connection and wait for the next request
		client.close
	rescue Exception => e
	end
end