Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial Commit

  • Loading branch information...
commit 1fa9f84d14022949d3cddcddb21049f21b0a9002 0 parents
PJ Davis authored
18 .bnsignore
@@ -0,0 +1,18 @@
+# The list of files that should be ignored by Mr Bones.
+# Lines that start with '#' are comments.
+#
+# A .gitignore file can be used instead by setting it as the ignore
+# file in your Rakefile:
+#
+# Bones {
+# ignore_file '.gitignore'
+# }
+#
+# For a project with a C extension, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+announcement.txt
+coverage
+doc
+pkg
7 .gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+announcement.txt
+coverage
+doc
+pkg
+*.gem
+**/.DS_Store
2  History.txt
@@ -0,0 +1,2 @@
+== 0.0.1 / 2010-04-14
+* winning at radius
67 README.rdoc
@@ -0,0 +1,67 @@
+== RADIUSTAR
+
+ ..................................................................................................
+ ..........................................................................H.......................
+ .........................................................................HHH......................
+ ........................................................................HHHHH.....................
+ ......................................................................HHHHHHHH....................
+ ...............................................................@_’’’@HHHHHHHHHHH3HdHdddHd’........
+ .......................................................dWE9p_^^&@d3HHHHHHHHHHHHHHHdHHHH@..........
+ ....................................................z933333HHHHHHHHHHHHHHHHHHHHddHdddd’...........
+ ..................................................pWp33Hdd@&&dHHHHHHHHHHHHHHHddHHHHd..............
+ ............................................33p3p3H@&&_@&@&&__^&3HHHHHHHHHHHHddHdH^...............
+ ........................................W3Hd&_&&_&@&_^@WN7WH3H_’.HHHHHHHHHHHHddHdd................
+ ...................................WWpHd@&___’’^^@HpNW7JJEENppd’&3HHHHHHHHHHHHddHd&...............
+ ................................pH@d&&&@&___^_&&d@HW7JzJEJJ7N3@’3HHHHHdHHHHHHHHHHHd...............
+ ........................3ppW3d@^_&__^^&&_^^_d3d39QzEEEE97EENp3&H333HHHdddHHH33HdHdH^..............
+ .....................dd@@dd@&&_^^&__&&d@dHddH@@3EJEWWN7W7EN3H&^p3HHdddHdH&...&dHHHdd..............
+ ................&&^__@&_@_^^_^^_&&dpWd3H&&&_HdpWW9EWppHWWHHH&^d3HHHHHHHH’........’_d’.............
+ ............_&@@@&&_^’’^_&@&HH33HpWW3dHd^^_’33pp97NWWHHHd@@^’^3HdddHHd^...........................
+ ........&d__@’’’’_^’’_&@&@&&3dH@@Hdd&_@____&@Hd@H@@_’’’’’....’^H&HHHH.............................
+ ....@@____^’^__^’&&@@@@H3d&@ddd@d^^^@__^d&_’.’’.................HHH...............................
+ Hdd@@____^_&&dd@@d_d__&&__&@^’&__^_’’.’...........................................................
+ @__&&__@&&&_&@@dH&^&^^^’^^^_^’’...................................................................
+ &_ddd@@@&___&&_&’^^’.’’...........................................................................
+
+by pjdavis
+http://github.com/pjdavis/radiustar
+
+== DESCRIPTION:
+
+Ruby Radius Library
+
+== FEATURES
+
+* Import your own radius dictionary
+* Test authentication
+* Check radius replies
+
+== COMING SOON
+
+* Use vendor specific attributes
+
+== SYNOPSIS:
+
+ require 'radiustar'
+
+ req = Radiustar::Request.new('server', 'my_ip_address')
+ resp = req.authenticate('my_user', 'my_password', 'shared_secret')
+ resp #=> true
+
+ attrs = req.get_attributes('my_user', 'my_password', 'shared_secret')
+ attrs #=> ["Access-Accept", {"Service-Type"=>"Framed-User",
+ "Framed-Protocol"=>"PPP",
+ "Framed-IP-Address"=>"255.255.255.254",
+ "Framed-IP-Netmask"=>"255.255.255.255"} ]
+
+== REQUIREMENTS:
+
+* Ruby 1.8
+
+== INSTALL:
+
+ gem install radiustar
+
+== LICENSE:
+
+Copyright (c) 2010 [PJ Davis], released under the CC0 1.0 Universal license.
20 Rakefile
@@ -0,0 +1,20 @@
+
+begin
+ require 'bones'
+rescue LoadError
+ abort '### Please install the "bones" gem ###'
+end
+
+task :default => 'test:run'
+task 'gem:release' => 'test:run'
+
+Bones {
+ name 'radiustar'
+ authors 'PJ Davis'
+ email 'pj.davis@gmail.com'
+ url 'http://github.com/pjdavis/radiustar'
+ ignore_file '.gitignore'
+ readme_file 'README.rdoc'
+
+}
+
65 lib/radiustar.rb
@@ -0,0 +1,65 @@
+
+module Radiustar
+
+ # :stopdoc:
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
+ # :startdoc:
+
+ # Returns the version string for the library.
+ #
+ def self.version
+ @version ||= File.read(path('version.txt')).strip
+ end
+
+ # Returns the library path for the module. If any arguments are given,
+ # they will be joined to the end of the libray path using
+ # <tt>File.join</tt>.
+ #
+ def self.libpath( *args, &block )
+ rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
+ if block
+ begin
+ $LOAD_PATH.unshift LIBPATH
+ rv = block.call
+ ensure
+ $LOAD_PATH.shift
+ end
+ end
+ return rv
+ end
+
+ # Returns the lpath for the module. If any arguments are given,
+ # they will be joined to the end of the path using
+ # <tt>File.join</tt>.
+ #
+ def self.path( *args, &block )
+ rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
+ if block
+ begin
+ $LOAD_PATH.unshift PATH
+ rv = block.call
+ ensure
+ $LOAD_PATH.shift
+ end
+ end
+ return rv
+ end
+
+ # Utility method used to require all files ending in .rb that lie in the
+ # directory below this file that has the same name as the filename passed
+ # in. Optionally, a specific _directory_ name can be passed in such that
+ # the _filename_ does not have to be equivalent to the directory.
+ #
+ def self.require_all_libs_relative_to( fname, dir = nil )
+ dir ||= ::File.basename(fname, '.*')
+ search_me = ::File.expand_path(
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
+
+ Dir.glob(search_me).sort.each {|rb| require rb}
+ end
+
+end # module Radiustar
+
+Radiustar.require_all_libs_relative_to(__FILE__)
+
66 lib/radiustar/dictionary.rb
@@ -0,0 +1,66 @@
+module Radiustar
+
+ class Dictionary
+
+ DEFAULT_DICTIONARY_PATH = ::File.join(::File.dirname(__FILE__), '..', '..', 'templates', 'default.txt')
+
+ def initialize(initial_path = nil)
+ @attributes = AttributesCollection.new
+
+ read initial_path if initial_path
+ end
+
+ def read(path)
+ file = File.open(path) do |f|
+ f.each_line do |line|
+ next if line =~ /^\#/ # discard comments
+ split_line = line.split(/\s+/)
+ next if split_line == []
+ case split_line.first.upcase
+ when "ATTRIBUTE"
+ set_attr(split_line)
+ when "VALUE"
+ set_value(split_line)
+ end
+ end
+ end
+
+ def find_attribute_by_name(name)
+ @attributes.find_by_name(name)
+ end
+
+ def find_attribute_by_id(id)
+ @attributes.find_by_id(id)
+ end
+
+ def attribute_name_defined?(name)
+ !@attributes.find_by_name(name).nil?
+ end
+
+ def attribute_id_defined?(id)
+ !@attributes.find_by_id(id).nil?
+ end
+
+ end
+
+ class << self
+
+ def default
+ new DEFAULT_DICTIONARY_PATH
+ end
+
+ end
+
+ private
+
+ def set_attr(line)
+ @attributes.add(line[1], line[2], line[3])
+ end
+
+ def set_value(line)
+ @attributes.find_by_name(line[1]).add_value(line[2], line[3])
+ end
+
+ end
+
+end
54 lib/radiustar/dictionary/attributes.rb
@@ -0,0 +1,54 @@
+module Radiustar
+
+ class AttributesCollection
+
+ def initialize
+ @collection = {}
+ @revcollection = []
+ end
+
+ def add(name, id, type)
+ @collection[name] ||= Attribute.new(name, id.to_i, type)
+ @revcollection[id.to_i] ||= @collection[name]
+ end
+
+ def find_by_name(name)
+ @collection[name]
+ end
+
+ def find_by_id(id)
+ @revcollection[id.to_i]
+ end
+
+ end
+
+ class Attribute
+
+ attr_reader :name, :id, :type
+
+ def initialize(name, id, type)
+ @values = ValuesCollection.new
+ @name = name
+ @id = id.to_i
+ @type = type
+ end
+
+ def add_value(name, id)
+ @values.add(name, id.to_i)
+ end
+
+ def find_values_by_name(name)
+ @values.find_by_name(name)
+ end
+
+ def find_values_by_id(id)
+ @values.find_by_id(id.to_i)
+ end
+
+ def has_values?
+ !@values.empty?
+ end
+
+ end
+
+end
39 lib/radiustar/dictionary/values.rb
@@ -0,0 +1,39 @@
+module Radiustar
+
+ class ValuesCollection
+
+ def initialize
+ @collection = {}
+ @revcollection = []
+ end
+
+ def add(name, id)
+ @collection[name] ||= Value.new(name, id)
+ @revcollection[id.to_i] ||= @collection[name]
+ end
+
+ def find_by_name(name)
+ @collection[name]
+ end
+
+ def find_by_id(id)
+ @revcollection[id]
+ end
+
+ def empty?
+ @collection.empty?
+ end
+
+ end
+
+ class Value
+ attr_accessor :name
+
+ def initialize(name, id)
+ @name = name
+ @id = id.to_i
+ end
+
+ end
+
+end
181 lib/radiustar/packet.rb
@@ -0,0 +1,181 @@
+module Radiustar
+
+ require 'digest/md5'
+
+ class Packet
+
+ CODES = { 'Access-Request' => 1, 'Access-Accept' => 2,
+ 'Access-Reject' => 3, 'Accounting-Request' => 4,
+ 'Accounting-Response' => 5, 'Access-Challenge' => 11,
+ 'Status-Server' => 12, 'Status-Client' => 13 }
+
+
+ HDRLEN = 1 + 1 + 2 + 16 # size of packet header
+ P_HDR = "CCna16a*" # pack template for header
+ P_ATTR = "CCa*" # pack template for attribute
+
+ attr_accessor :code
+ attr_reader :id, :attributes
+
+ def initialize(dictionary, id, data = nil)
+ @dict = dictionary
+ @id = id
+ unset_all_attributes
+ if data
+ @packed = data
+ self.unpack
+ end
+ self
+ end
+
+ def increment_id
+ @id = (@id + 1) & 0xff
+ end
+
+ def to_a
+ @attributes.to_a
+ end
+
+ # Generate an authenticator. It will try to use /dev/urandom if
+ # possible, or the system rand call if that's not available.
+ def gen_authenticator
+ authenticator = []
+ 8.times do
+ authenticator << rand(65536)
+ end
+ authenticator.pack("n8")
+ if (File.exist?("/dev/urandom"))
+ File.open("/dev/urandom") do |urandom|
+ authenticator = urandom.read(16)
+ end
+ end
+ @authenticator = authenticator
+ end
+
+ def set_attribute(name, value)
+ @attributes[name] = value
+ end
+
+ def unset_attribute(name)
+ @attributes[name] = nil
+ end
+
+ def attribute(name)
+ @attributes[name]
+ end
+
+ def unset_all_attributes
+ @attributes = Hash.new
+ end
+
+ def set_encoded_attribute(name, value, secret)
+ @attributes[name] = encode(value, secret)
+ end
+
+ def pack
+
+ attstr = ""
+ @attributes.each_pair do |attribute, value|
+ attribute = @dict.find_attribute_by_name(attribute)
+ anum = attribute.id
+ val = case attribute.type
+ when "string"
+ value
+ when "integer"
+ [attribute.has_values? ? attribute.find_values_by_id(value) : value].pack("N")
+ when "ipaddr"
+ [inet_aton(value)].pack("N")
+ when "date"
+ [value].pack("N")
+ when "time"
+ [value].pack("N")
+ else
+ next
+ end
+ attstr += [attribute.id, val.length + 2, val].pack(P_ATTR)
+ end
+
+ @packed = [CODES[@code], @id, attstr.length + HDRLEN, @authenticator, attstr].pack(P_HDR)
+ end
+
+ protected
+
+ def unpack
+ @code, @id, len, @authenticator, attribute_data = @packed.unpack(P_HDR)
+ @code = CODES.index(@code)
+
+ unset_all_attributes
+
+ while attribute_data.length > 0 do
+ length = attribute_data.unpack("xC").first.to_i
+ attribute_type, attribute_value = attribute_data.unpack("Cxa#{length-2}")
+ attribute_type = attribute_type.to_i
+
+ attribute = @dict.find_attribute_by_id(attribute_type)
+ attribute_value = case attribute.type
+ when 'string'
+ attribute_value
+ when 'integer'
+ attribute.has_values? ? attribute.find_values_by_id(attribute_value.unpack("N")[0]).name : attribute_value.unpack("N")[0]
+ when 'ipaddr'
+ inet_ntoa(attribute_value.unpack("N")[0])
+ when 'time'
+ attribute_value.unpack("N")[0]
+ when 'date'
+ attribute_value.unpack("N")[0]
+ end
+
+ set_attribute(attribute.name, attribute_value)
+ attribute_data[0, length] = ""
+ end
+ end
+
+ def inet_aton(hostname)
+ if (hostname =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)
+ return (($1.to_i & 0xff) << 24) + (($2.to_i & 0xff) << 16) + (($3.to_i & 0xff) << 8) + (($4.to_i & 0xff))
+ end
+ 0
+ end
+
+ def inet_ntoa(iaddr)
+ sprintf("%d.%d.%d.%d", (iaddr >> 24) & 0xff, (iaddr >> 16) & 0xff, (iaddr >> 8) & 0xff, (iaddr) & 0xff)
+ end
+
+ def xor_str(str1, str2)
+ i = 0
+ newstr = ""
+ str1.each_byte do |c1|
+ c2 = str2[i]
+ newstr = newstr << (c1 ^ c2)
+ i = i+1
+ end
+ newstr
+ end
+
+ def encode(value, secret)
+ lastround = @authenticator
+ encoded_value = ""
+ # pad to 16n bytes
+ value += "\000" * (15-(15 + value.length) % 16)
+ 0.step(value.length-1, 16) do |i|
+ lastround = xor_str(value[i, 16], Digest::MD5.digest(secret + lastround) )
+ encoded_value += lastround
+ end
+ encoded_value
+ end
+
+ def decode(value, secret)
+ decoded_value = ""
+ lastround = @authenticator
+ 0.step(value.length-1, 16) do |i|
+ decoded_value = xor_str(value[i, 16], Digest::MD5.digest(secret + lastround))
+ lastround = value[i, 16]
+ end
+
+ decoded_value.gsub!(/\000+/, "") if decoded_value
+ decoded_value[value.length, -1] = "" unless (decoded_value.length <= value.length)
+ return decoded_value
+ end
+
+ end
+end
3  lib/radiustar/radiustar.rb
@@ -0,0 +1,3 @@
+module Radiustar
+
+end
66 lib/radiustar/request.rb
@@ -0,0 +1,66 @@
+module Radiustar
+
+ require 'socket'
+
+ class Request
+
+ def initialize(server, my_ip, dict_file = nil)
+ @dict = dict_file.nil? ? Dictionary.default : Dictionary.new(dict_file)
+ @my_ip = my_ip
+ @host, @port = server.split(":")
+ @port = Socket.getservbyname("radius", "udp") unless @port
+ @port = 1812 unless @port
+ @port = @port.to_i # just in case
+ @socket = UDPSocket.open
+ @socket.connect(@host, @port)
+ end
+
+ def authenticate(name, password, secret)
+ @packet = Packet.new(@dict, Process.pid & 0xff)
+ @packet.code = 'Access-Request'
+ @packet.gen_authenticator
+ @packet.set_attribute('User-Name', name)
+ @packet.set_attribute('NAS-IP-Address', @my_ip)
+ @packet.set_encoded_attribute('User-Password', password, secret)
+ send_packet
+ @recieved_packet = recv_packet
+ return @recieved_packet.code == 'Access-Accept'
+ end
+
+ def get_attributes(name, password, secret)
+ @packet = Packet.new(@dict, Process.pid & 0xff)
+ @packet.code = 'Access-Request'
+ @packet.gen_authenticator
+ @packet.set_attribute('User-Name', name)
+ @packet.set_attribute('NAS-IP-Address', @my_ip)
+ @packet.set_encoded_attribute('User-Password', password, secret)
+ send_packet
+ @recieved_packet = recv_packet
+ recieved_thing = [@recieved_packet.code]
+ recieved_thing << @recieved_packet.attributes
+ end
+
+ def inspect
+ to_s
+ end
+
+ private
+
+ def send_packet
+ data = @packet.pack
+ @packet.increment_id
+ @socket.send(data, 0)
+ end
+
+ def recv_packet
+ if select([@socket], nil, nil, 60) == nil
+ raise "Timed out waiting for response packet from server"
+ end
+ data = @socket.recvfrom(64)
+ Packet.new(@dict, Process.pid & 0xff, data[0])
+ end
+
+
+ end
+
+end
6 spec/radiustar_spec.rb
@@ -0,0 +1,6 @@
+
+require File.join(File.dirname(__FILE__), %w[spec_helper])
+
+describe Radiustar do
+end
+
15 spec/spec_helper.rb
@@ -0,0 +1,15 @@
+
+require File.expand_path(
+ File.join(File.dirname(__FILE__), %w[.. lib radiustar]))
+
+Spec::Runner.configure do |config|
+ # == Mock Framework
+ #
+ # RSpec uses it's own mocking framework by default. If you prefer to
+ # use mocha, flexmock or RR, uncomment the appropriate line:
+ #
+ # config.mock_with :mocha
+ # config.mock_with :flexmock
+ # config.mock_with :rr
+end
+
0  test/test_radiustar.rb
No changes.
1  version.txt
@@ -0,0 +1 @@
+0.0.1
Please sign in to comment.
Something went wrong with that request. Please try again.