/radiuslib

A complete RADIUS protocol and utility library for Ruby.

Primary LanguageRubyGNU General Public License v2.0GPL-2.0

This is my version of radiuslib, converted for use as a rails plugin/gem.
The source was originally found here: http://raa.ruby-lang.org/project/radiuslib/

I use this library as a basic RADIUS client plugin for my rails application.
Some bug fixes have been made. The features have not been changed from the
original author's intention. I am unaware if all advertised features work, as
the original project apparently died before progressing past beta, and the
author does not seem to be around or maintain it anymore. My hope is that
someone else will benefit from my experience with this library.

The original README is below:


RADIUSlib for Ruby

Aug 5, 2002

Dan Debertin <airboss@nodewarrior.org>

---------------------------

Introduction

---------------------------

This is the README for RADIUSlib version 0.5. This will (hopefully) be
the only "zero-dot" release of RADIUSlib; i'm going to let this
incubate for a few months and then release a 1.0 in November or
October of 2002.

RADIUSlib is a full-featured RADIUS protocol and utility library for
the Ruby programming language. It includes the following components:

o   A complete, RFC-compliant implementation of the protocol,
appropriate for use as a client or server. Both authentication
(RFC2865) and accounting (RFC2866) are supported.

o   A higher-level RADIUS request handler that encapsulates the tasks
to process a single RADIUS authentication or accounting
transaction.

o   A dictionary parser supporting almost all available formats in
use today.

o   A r/w parser for Cistron-format "users" files, commonly used to
store user authentication information. 

o   A parser for RADIUS accounting "detail" files. This is another
standard format for RADIUS servers to log accounting data.

RADIUSlib is, to my knowledge, the most featureful RADIUS
protocol/utility library available to Ruby users (though not the
first; someone else beat me to that by a month or so ;), and also
soundly trounces similar offerings for the other high-level scripting
languages. Its notable features include:

o   Full VSA support, even those quirky USR VSAs.
o   Authentication classes support both PAP and CHAP methods.
o   Encodes and decodes filters in the Ascend binary filter format
    ("abinary").
o   Idiomatic, Ruby-ish access. I tried to implement lots of useful
    iterators, and most classes that represent indexable data can
    be accessed with the familiar Hash-like "[]" and "[]=" methods.
o   "Request" classes include client-side networking.

----------------------------

Installation

----------------------------

$ ruby install.rb config
$ ruby install.rb setup
# ruby install.rb install

Unfortunately, install.rb doesn't come with an `uninstall'
target. This should do the trick though, modified for your
environment: 

# rm -r /usr/local/lib/ruby/site_ruby/1.6/radius


There are no prerequisites apart from Ruby itself.

I doubt that RADIUSlib works on non-UNIX systems, and I don't have any
non-UNIX systems to port it to. Volunteers welcome.

----------------------------

Contact (bugs, questions, etc.)

----------------------------

There is no mailing list. Just send mail to airboss@nodewarrior.org if
you have questions or bug reports; I don't bite. Well, not usually.


----------------------------

Use

----------------------------

The best documentation is the rdoc in the source. A pre-processed HTML
version is included with the source in the doc/ directory. 

Nevertheless, I'll go over some simple examples of how to use each
class here. Take a look at the 'radtest.rb' program in the sample/
subdirectory for more use examples. 

-=
RADIUS::Dictionary
-=

This class is used by almost all of the other classes in this
library. Dictionaries are used by RADIUS clients and servers to map
between human-readable attribute-names and the numbers that appear on
the wire. Sort of a non-distributed naming system...

You can use the dictionaries included with this library (in the
raddb/ subdirectory), or the set that came with your RADIUS server.

A RADIUS dictionary is instantiated like this:

require "radius/dictionary"
dict = RADIUS::Dictionary.new("/path/to/my/dict")

Make sure that all other dictionaries pulled in by your main
dictionary via $INCLUDE directives live in either your current
directory, or the same directory as your main dictionary.

After the object is initialized, it can then be accessed as if it were
a hash. The structures returned are documented in
radius/radiusutil.rb. I would recommend reading the rdoc if you're
going to be using this class extensively.

-=
RADIUS::AuthPacket, RADIUS::AcctPacket
-=

You shouldn't need to use these classes in most cases; if you're just
sending authentication or accounting requests, the higher-level
RADIUS::AuthRequest and RADIUS::AcctRequest class are easier to
use. The packet-level classes are still pretty friendly -- you never
have to deal with binary data -- but they won't do convenient things
for you like auto-encrypting passwords and doing the networking
for you.

A RADIUS authentication packet is instantiated like this:

require "radius/packet"
packet = RADIUS::AuthPacket.new(dict_obj, code, "secret", binary_packet)

dict_obj can be either an existing RADIUS::Dictionary object or the
path to an actual dictionary. 'code' is the type of packet we're
sending; the codes can be found in your RFCs, or at the top of
radius/packet.rb. The last argument is optional, and is used when you
have an existing RADIUS packet that you want to initialize as a
RADIUS::Packet object -- for example, when you've received a response
from a remote client/server off the wire.

IMPORTANT: If you're initializing an ACCESS_ACCEPT or ACCESS_REJECT
packet, it's vital that you call the #response_to method with an
argument of the request you're responding to. RADIUS needs access to
the identifier and request authenticator from that request in order to
generate the appropriate MD5 hash for the response authenticator. See
the rdoc.

You may find it more intuitive to use these "helper" constructors:

RADIUS::AuthPacket.access_request(dict_obj, "secret", binary_request_packet)
RADIUS::AuthPacket.access_accept(dict_obj, "secret", binary_accept_packet, binary_request_packet)
RADIUS::AuthPacket.access_reject(dict_obj, "secret", binary_reject_packetm, binary_request_packet)

This prevents you from having to know the packet type codes, and in
the last two, will call #response_to for you if the original request
packet is supplied as the last argument.

After that, you can add attributes to your packet:

packet['User-Name'] = 'radiustest'
packet[7] = "PPP"
packet['Cisco-Pre-Input-Octets'] = 9487
packet['Ascend-Data-Filter'] = 'ip in drop dstip 127.0.0.1/32'

packet['Framed-Protocol']  # ["PPP"]

Three things to note. One, you can refer to dictionary attributes by
name or by number, with the exception of VSAs, which you should only
refer to by name. Two, VSAs are added and accessed just like normal
attributes -- all of the special handling occurs below the
surface. Three, because of the possibility of having more than one
instance of an attribute in a given packet, values are returned in an
array even if there's only one value to access.

RADIUS::AcctPacket doesn't behave appreciably different from
AuthPacket. Just read the rdoc.

-=
RADIUS::AuthRequest, RADIUS::AcctRequest
-=

These classes encapsulate an entire authentication or accounting
transaction -- building the request, sending it to a server, parsing
and verifying the response and giving access to it. Ideally, a RADIUS
authentication request should be as simple as this:

require "radius/request"
req = RADIUS::AuthRequest.new("dict/dictionary", "radiusserver", "testing123")
req['User-Name'] = 'radiustest'
req['CHAP-Password'] = 'foobar'

if(req.send)
  if(req.success?)
    req.each { |a, v| puts "\t#{a} = #{v}" }
  else
    puts "Access denied."
  end
else
  puts "No response received from server."
end

As you can see, #success? will return true if a positive response was
received, and false if a negative response was received. #send will
return false if no response was received (#success? will return nil in
this case). Before you call #send, any accesses ([]) or assignments
([]=) that you do to the request will access/assign to the
request. After #send, they will access/assign to the response.

Again, RADIUS::AcctRequest behaves much like RADIUS::AuthRequest.

-=
RADIUS::Usersfile
-=

This class provides simple access to Cistron-format "users" files. If
you have a RADIUS server installation, this file will probably live in
/etc/raddb, though a small sample is included with this package in the
sample/ directory. 

As mentioned above, this class allows you to both read and write
"users" files. Here is a sample session, in which we access an
existing users file, make some changes, and save it to disk.

require "radius/usersfile"
# Pre-initialize a dictionary.
$dict = RADIUS::Dictionary.new("dict/dictionary")
# Create the usersfile object.
myusers = RADIUS::Usersfile.new($dict, "/etc/raddb/users")
# Change testuser1's password.
myusers['testuser1']['Password'] = 'xyzzy'
# Change testuser2's idle timeout.
myusers['testuser2'].delete('Idle-Timeout')
myusers['testuser2']['Idle-Timeout'] = 900
# Update the file on disk and refresh our in-memory data.
myusers.update

Performance note: An instance of this class will parse the entire file
when #new is called. This means that you may see a small delay when
processing large files, as a RADIUS::User object must be created for
every entry in the file. Access after that point should be quite fast,
though. 

-=
RADIUS::Detail
-=

This class provides read-only access to RADIUS "detail" files. If
you're using Cistron, you would find these files under
/var/log/radacct typically. For Livingston, they're usually under
/usr/adm. It's a very simple class; you should be able to glean
everything you need to know from the rdoc. Here's a sample use:

# Total the length of every session in the log.

require "radius/detail"
totaltime = 0
mylog = RADIUS::Detail.new("/var/log/radacct/nas1/detail")
mylog.each do |rec|
  next if(! rec.stop?)    # Only care about Stop records.
  totaltime += rec['Acct-Session-Time'][0].to_i
end

Performance note: As this class is RO, I was able to make some
optimizations for speed -- caching, instantiate-on-access,
etc. Important, as some RADIUS logs can be quite large. Nevertheless,
huge logfiles (100's of MB) being parsed on slow machines can slow
#new down significantly.

-------------------------

Have fun. Send packets. Report bugs.

Dan Debertin
<airboss@nodewarrior.org>