dguerri-radiustar 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
- data/History.txt +11 -0
- data/README.rdoc +90 -0
- data/Rakefile +20 -0
- data/lib/radiustar.rb +65 -0
- data/lib/radiustar/dictionary.rb +140 -0
- data/lib/radiustar/dictionary/attributes.rb +61 -0
- data/lib/radiustar/dictionary/values.rb +43 -0
- data/lib/radiustar/packet.rb +210 -0
- data/lib/radiustar/radiustar.rb +7 -0
- data/lib/radiustar/request.rb +124 -0
- data/lib/radiustar/vendor.rb +60 -0
- data/radiustar.gemspec +34 -0
- data/spec/radiustar_spec.rb +16 -0
- data/spec/spec_helper.rb +15 -0
- data/templates/default.txt +207 -0
- data/templates/dictionary.digium +36 -0
- data/templates/gandalf.dictionary +105 -0
- data/test/test_radiustar.rb +0 -0
- data/version.txt +1 -0
- metadata +106 -0
data/.gitignore
ADDED
data/History.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
== 0.0.4 / 2010-12/04
|
|
2
|
+
* github fork (dguerri)
|
|
3
|
+
* bugfixes
|
|
4
|
+
* added multiple dictionary files support
|
|
5
|
+
* added accounting support
|
|
6
|
+
|
|
7
|
+
== 0.0.3 / 2010-06-30
|
|
8
|
+
* no longer require to specify your own IP (via bwlang)
|
|
9
|
+
|
|
10
|
+
== 0.0.1 / 2010-04-14
|
|
11
|
+
* winning at radius
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
== RADIUSTAR
|
|
2
|
+
|
|
3
|
+
..................................................................................................
|
|
4
|
+
..........................................................................H.......................
|
|
5
|
+
.........................................................................HHH......................
|
|
6
|
+
........................................................................HHHHH.....................
|
|
7
|
+
......................................................................HHHHHHHH....................
|
|
8
|
+
...............................................................@_’’’@HHHHHHHHHHH3HdHdddHd’........
|
|
9
|
+
.......................................................dWE9p_^^&@d3HHHHHHHHHHHHHHHdHHHH@..........
|
|
10
|
+
....................................................z933333HHHHHHHHHHHHHHHHHHHHddHdddd’...........
|
|
11
|
+
..................................................pWp33Hdd@&&dHHHHHHHHHHHHHHHddHHHHd..............
|
|
12
|
+
............................................33p3p3H@&&_@&@&&__^&3HHHHHHHHHHHHddHdH^...............
|
|
13
|
+
........................................W3Hd&_&&_&@&_^@WN7WH3H_’.HHHHHHHHHHHHddHdd................
|
|
14
|
+
...................................WWpHd@&___’’^^@HpNW7JJEENppd’&3HHHHHHHHHHHHddHd&...............
|
|
15
|
+
................................pH@d&&&@&___^_&&d@HW7JzJEJJ7N3@’3HHHHHdHHHHHHHHHHHd...............
|
|
16
|
+
........................3ppW3d@^_&__^^&&_^^_d3d39QzEEEE97EENp3&H333HHHdddHHH33HdHdH^..............
|
|
17
|
+
.....................dd@@dd@&&_^^&__&&d@dHddH@@3EJEWWN7W7EN3H&^p3HHdddHdH&...&dHHHdd..............
|
|
18
|
+
................&&^__@&_@_^^_^^_&&dpWd3H&&&_HdpWW9EWppHWWHHH&^d3HHHHHHHH’........’_d’.............
|
|
19
|
+
............_&@@@&&_^’’^_&@&HH33HpWW3dHd^^_’33pp97NWWHHHd@@^’^3HdddHHd^...........................
|
|
20
|
+
........&d__@’’’’_^’’_&@&@&&3dH@@Hdd&_@____&@Hd@H@@_’’’’’....’^H&HHHH.............................
|
|
21
|
+
....@@____^’^__^’&&@@@@H3d&@ddd@d^^^@__^d&_’.’’.................HHH...............................
|
|
22
|
+
Hdd@@____^_&&dd@@d_d__&&__&@^’&__^_’’.’...........................................................
|
|
23
|
+
@__&&__@&&&_&@@dH&^&^^^’^^^_^’’...................................................................
|
|
24
|
+
&_ddd@@@&___&&_&’^^’.’’...........................................................................
|
|
25
|
+
|
|
26
|
+
by pjdavis
|
|
27
|
+
http://github.com/pjdavis/radiustar
|
|
28
|
+
|
|
29
|
+
== DESCRIPTION:
|
|
30
|
+
|
|
31
|
+
Ruby Radius Library
|
|
32
|
+
|
|
33
|
+
== FEATURES
|
|
34
|
+
|
|
35
|
+
* Import your own radius dictionaries
|
|
36
|
+
* Authentication
|
|
37
|
+
* Accounting
|
|
38
|
+
|
|
39
|
+
== SYNOPSIS:
|
|
40
|
+
|
|
41
|
+
require 'rubygems'
|
|
42
|
+
require 'radiustar'
|
|
43
|
+
|
|
44
|
+
# Load dictionaries from freeradius directory
|
|
45
|
+
dict = Radiustar::Dictionary.new('/usr/share/freeradius/')
|
|
46
|
+
|
|
47
|
+
# Lets get authenticated
|
|
48
|
+
auth_custom_attr = {
|
|
49
|
+
'Framed-Address' => '127.0.0.1',
|
|
50
|
+
'NAS-Port' => 0,
|
|
51
|
+
'NAS-Port-Type' => 'Ethernet'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
req = Radiustar::Request.new('127.0.0.1', { :dict => dict })
|
|
55
|
+
reply = req.authenticate('John Doe', 'hello', 'testing123', auth_custom_attr)
|
|
56
|
+
|
|
57
|
+
if reply[:code] == 'Access-Accept'
|
|
58
|
+
req = Radiustar::Request.new('127.0.0.1:1813', { :dict => dict })
|
|
59
|
+
|
|
60
|
+
acct_custom_attr = {
|
|
61
|
+
'Framed-Address' => '127.0.0.1',
|
|
62
|
+
'NAS-Port' => 0,
|
|
63
|
+
'NAS-Port-Type' => 'Ethernet',
|
|
64
|
+
'Acct-Session-Time' => 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
timings = Time.now
|
|
68
|
+
reply = req.accounting_start('John Doe', 'testing123', '123456', acct_custom_attr)
|
|
69
|
+
|
|
70
|
+
sleep(rand 5)
|
|
71
|
+
acct_custom_attr['Acct-Session-Time'] = Time.now - timings
|
|
72
|
+
reply = req.accounting_update('John Doe', 'testing123', '123456', acct_custom_attr)
|
|
73
|
+
|
|
74
|
+
sleep(rand 5)
|
|
75
|
+
acct_custom_attr['Acct-Session-Time'] = Time.now - timings
|
|
76
|
+
reply = req.accounting_stop('John Doe', 'testing123', '123456', acct_custom_attr)
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
== REQUIREMENTS:
|
|
81
|
+
|
|
82
|
+
* Ruby 1.8
|
|
83
|
+
|
|
84
|
+
== INSTALL:
|
|
85
|
+
|
|
86
|
+
gem install radiustar
|
|
87
|
+
|
|
88
|
+
== LICENSE:
|
|
89
|
+
|
|
90
|
+
Copyright (c) 2010 [PJ Davis], released under the CC0 1.0 Universal license.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
begin
|
|
3
|
+
require 'bones'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
abort '### Please install the "bones" gem ###'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
task :default => 'test:run'
|
|
9
|
+
task 'gem:release' => 'test:run'
|
|
10
|
+
|
|
11
|
+
Bones {
|
|
12
|
+
name 'dguerri-radiustar'
|
|
13
|
+
authors 'PJ Davis', 'Davide Guerri'
|
|
14
|
+
email 'davide.guerri@gmail.com'
|
|
15
|
+
url 'http://github.com/dguerri/radiustar'
|
|
16
|
+
ignore_file '.gitignore'
|
|
17
|
+
readme_file 'README.rdoc'
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
data/lib/radiustar.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
module Radiustar
|
|
3
|
+
|
|
4
|
+
# :stopdoc:
|
|
5
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
|
6
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
|
7
|
+
# :startdoc:
|
|
8
|
+
|
|
9
|
+
# Returns the version string for the library.
|
|
10
|
+
#
|
|
11
|
+
def self.version
|
|
12
|
+
@version ||= File.read(path('version.txt')).strip
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns the library path for the module. If any arguments are given,
|
|
16
|
+
# they will be joined to the end of the libray path using
|
|
17
|
+
# <tt>File.join</tt>.
|
|
18
|
+
#
|
|
19
|
+
def self.libpath( *args, &block )
|
|
20
|
+
rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
|
21
|
+
if block
|
|
22
|
+
begin
|
|
23
|
+
$LOAD_PATH.unshift LIBPATH
|
|
24
|
+
rv = block.call
|
|
25
|
+
ensure
|
|
26
|
+
$LOAD_PATH.shift
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
return rv
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the lpath for the module. If any arguments are given,
|
|
33
|
+
# they will be joined to the end of the path using
|
|
34
|
+
# <tt>File.join</tt>.
|
|
35
|
+
#
|
|
36
|
+
def self.path( *args, &block )
|
|
37
|
+
rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
|
38
|
+
if block
|
|
39
|
+
begin
|
|
40
|
+
$LOAD_PATH.unshift PATH
|
|
41
|
+
rv = block.call
|
|
42
|
+
ensure
|
|
43
|
+
$LOAD_PATH.shift
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
return rv
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Utility method used to require all files ending in .rb that lie in the
|
|
50
|
+
# directory below this file that has the same name as the filename passed
|
|
51
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
|
52
|
+
# the _filename_ does not have to be equivalent to the directory.
|
|
53
|
+
#
|
|
54
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
|
55
|
+
dir ||= ::File.basename(fname, '.*')
|
|
56
|
+
search_me = ::File.expand_path(
|
|
57
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
|
58
|
+
|
|
59
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end # module Radiustar
|
|
63
|
+
|
|
64
|
+
Radiustar.require_all_libs_relative_to(__FILE__)
|
|
65
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module Radiustar
|
|
2
|
+
|
|
3
|
+
class Dictionary
|
|
4
|
+
|
|
5
|
+
include Radiustar
|
|
6
|
+
|
|
7
|
+
DEFAULT_DICTIONARIES_PATH = ::File.join(::File.dirname(__FILE__), '..', '..', 'templates')
|
|
8
|
+
|
|
9
|
+
def initialize(initial_path = nil)
|
|
10
|
+
@attributes = AttributesCollection.new
|
|
11
|
+
@vendors = VendorCollection.new
|
|
12
|
+
|
|
13
|
+
read_files initial_path if initial_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read_files(path)
|
|
17
|
+
dict_files = File.join(path, "*")
|
|
18
|
+
Dir.glob(dict_files) { |file|
|
|
19
|
+
read_attributes(file)
|
|
20
|
+
}
|
|
21
|
+
Dir.glob(dict_files) { |file|
|
|
22
|
+
read_values(file)
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def find_attribute_by_name(name)
|
|
27
|
+
@attributes.find_by_name(name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def find_attribute_by_id(id)
|
|
31
|
+
@attributes.find_by_id(id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def attribute_name_defined?(name)
|
|
35
|
+
!@attributes.find_by_name(name).nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def attribute_id_defined?(id)
|
|
39
|
+
!@attributes.find_by_id(id).nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def vendors
|
|
43
|
+
@vendors
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def attributes
|
|
47
|
+
@attributes
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def name
|
|
51
|
+
"Dictionary"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class << self
|
|
55
|
+
|
|
56
|
+
def default
|
|
57
|
+
new DEFAULT_DICTIONARIES_PATH
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
protected
|
|
63
|
+
|
|
64
|
+
def read_attributes(path)
|
|
65
|
+
file = File.open(path) do |f|
|
|
66
|
+
current_vendor = nil
|
|
67
|
+
f.each_line do |line|
|
|
68
|
+
next if line =~ /^\#/ # discard comments
|
|
69
|
+
split_line = line.split(/\s+/)
|
|
70
|
+
next if split_line == []
|
|
71
|
+
case split_line.first.upcase
|
|
72
|
+
when "ATTRIBUTE"
|
|
73
|
+
current_vendor.nil? ? set_attr(split_line) : set_vendor_attr(current_vendor, split_line)
|
|
74
|
+
when "VENDOR"
|
|
75
|
+
add_vendor(split_line)
|
|
76
|
+
when "BEGIN-VENDOR"
|
|
77
|
+
current_vendor = set_vendor(split_line)
|
|
78
|
+
when "END-VENDOR"
|
|
79
|
+
current_vendor = nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def read_values(path)
|
|
86
|
+
file = File.open(path) do |f|
|
|
87
|
+
current_vendor = nil
|
|
88
|
+
f.each_line do |line|
|
|
89
|
+
next if line =~ /^\#/ # discard comments
|
|
90
|
+
split_line = line.split(/\s+/)
|
|
91
|
+
next if split_line == []
|
|
92
|
+
case split_line.first.upcase
|
|
93
|
+
when "VALUE"
|
|
94
|
+
if current_vendor.nil?
|
|
95
|
+
set_value(split_line)
|
|
96
|
+
else
|
|
97
|
+
begin
|
|
98
|
+
set_vendor_value(current_vendor, split_line)
|
|
99
|
+
rescue
|
|
100
|
+
set_value(split_line)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
when "BEGIN-VENDOR"
|
|
104
|
+
current_vendor = set_vendor(split_line)
|
|
105
|
+
when "END-VENDOR"
|
|
106
|
+
current_vendor = nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def set_attr(line)
|
|
115
|
+
@attributes.add(line[1], line[2], line[3])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def set_value(line)
|
|
119
|
+
@attributes.find_by_name(line[1]).add_value(line[2], line[3])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def add_vendor(line)
|
|
123
|
+
@vendors.add(line[2], line[1])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def set_vendor(line)
|
|
127
|
+
@vendors.find_by_name(line[1])
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def set_vendor_attr(vendor, line)
|
|
131
|
+
vendor.add_attribute(line[1], line[2], line[3])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def set_vendor_value(vendor, line)
|
|
135
|
+
vendor.find_attribute_by_name(line[1]).add_value(line[2], line[3])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Radiustar
|
|
2
|
+
|
|
3
|
+
class AttributesCollection < Array
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@collection = {}
|
|
7
|
+
@revcollection = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add(name, id, type)
|
|
11
|
+
@collection[name] ||= Attribute.new(name, id.to_i, type)
|
|
12
|
+
@revcollection[id.to_i] ||= @collection[name]
|
|
13
|
+
self << @collection[name]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find_by_name(name)
|
|
17
|
+
@collection[name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_by_id(id)
|
|
21
|
+
@revcollection[id]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Attribute
|
|
27
|
+
|
|
28
|
+
include Radiustar
|
|
29
|
+
|
|
30
|
+
attr_reader :name, :id, :type
|
|
31
|
+
|
|
32
|
+
def initialize(name, id, type)
|
|
33
|
+
@values = ValuesCollection.new
|
|
34
|
+
@name = name
|
|
35
|
+
@id = id.to_i
|
|
36
|
+
@type = type
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_value(name, id)
|
|
40
|
+
@values.add(name, id.to_i)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def find_values_by_name(name)
|
|
44
|
+
@values.find_by_name(name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def find_values_by_id(id)
|
|
48
|
+
@values.find_by_id(id.to_i)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def has_values?
|
|
52
|
+
!@values.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def values
|
|
56
|
+
@values
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Radiustar
|
|
2
|
+
|
|
3
|
+
class ValuesCollection < Array
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@collection = {}
|
|
7
|
+
@revcollection = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add(name, id)
|
|
11
|
+
@collection[name] ||= Value.new(name, id)
|
|
12
|
+
@revcollection[id.to_i] ||= @collection[name]
|
|
13
|
+
self << @collection[name]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find_by_name(name)
|
|
17
|
+
@collection[name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_by_id(id)
|
|
21
|
+
@revcollection[id]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def empty?
|
|
25
|
+
@collection.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Value
|
|
31
|
+
|
|
32
|
+
include Radiustar
|
|
33
|
+
|
|
34
|
+
attr_accessor :name, :id
|
|
35
|
+
|
|
36
|
+
def initialize(name, id)
|
|
37
|
+
@name = name
|
|
38
|
+
@id = id.to_i
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
module Radiustar
|
|
2
|
+
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
|
|
5
|
+
class Packet
|
|
6
|
+
|
|
7
|
+
CODES = { 'Access-Request' => 1, 'Access-Accept' => 2,
|
|
8
|
+
'Access-Reject' => 3, 'Accounting-Request' => 4,
|
|
9
|
+
'Accounting-Response' => 5, 'Access-Challenge' => 11,
|
|
10
|
+
'Status-Server' => 12, 'Status-Client' => 13 }
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
HDRLEN = 1 + 1 + 2 + 16 # size of packet header
|
|
14
|
+
P_HDR = "CCna16a*" # pack template for header
|
|
15
|
+
P_ATTR = "CCa*" # pack template for attribute
|
|
16
|
+
|
|
17
|
+
attr_accessor :code
|
|
18
|
+
attr_reader :id, :attributes
|
|
19
|
+
|
|
20
|
+
def initialize(dictionary, id, data = nil)
|
|
21
|
+
@dict = dictionary
|
|
22
|
+
@id = id
|
|
23
|
+
unset_all_attributes
|
|
24
|
+
if data
|
|
25
|
+
@packed = data
|
|
26
|
+
self.unpack
|
|
27
|
+
end
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def increment_id
|
|
32
|
+
@id = (@id + 1) & 0xff
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_a
|
|
36
|
+
@attributes.to_a
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Generate an authenticator. It will try to use /dev/urandom if
|
|
40
|
+
# possible, or the system rand call if that's not available.
|
|
41
|
+
def gen_auth_authenticator
|
|
42
|
+
if (File.exist?("/dev/urandom"))
|
|
43
|
+
File.open("/dev/urandom") do |urandom|
|
|
44
|
+
@authenticator = urandom.read(16)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
@authenticator = []
|
|
48
|
+
8.times do
|
|
49
|
+
@authenticator << rand(65536)
|
|
50
|
+
end
|
|
51
|
+
@authenticator = @authenticator.pack("n8")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def gen_acct_authenticator(secret)
|
|
56
|
+
# From RFC2866
|
|
57
|
+
# Request Authenticator
|
|
58
|
+
#
|
|
59
|
+
# In Accounting-Request Packets, the Authenticator value is a 16
|
|
60
|
+
# octet MD5 [5] checksum, called the Request Authenticator.
|
|
61
|
+
#
|
|
62
|
+
# The NAS and RADIUS accounting server share a secret. The Request
|
|
63
|
+
# Authenticator field in Accounting-Request packets contains a one-
|
|
64
|
+
# way MD5 hash calculated over a stream of octets consisting of the
|
|
65
|
+
# Code + Identifier + Length + 16 zero octets + request attributes +
|
|
66
|
+
# shared secret (where + indicates concatenation). The 16 octet MD5
|
|
67
|
+
# hash value is stored in the Authenticator field of the
|
|
68
|
+
# Accounting-Request packet.
|
|
69
|
+
#
|
|
70
|
+
# Note that the Request Authenticator of an Accounting-Request can
|
|
71
|
+
# not be done the same way as the Request Authenticator of a RADIUS
|
|
72
|
+
# Access-Request, because there is no User-Password attribute in an
|
|
73
|
+
# Accounting-Request.
|
|
74
|
+
#
|
|
75
|
+
@authenticator = "\000"*16
|
|
76
|
+
@authenticator = Digest::MD5.digest(pack + secret)
|
|
77
|
+
@packed = nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def set_attribute(name, value)
|
|
81
|
+
@attributes[name] = value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def unset_attribute(name)
|
|
85
|
+
@attributes[name] = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def attribute(name)
|
|
89
|
+
@attributes[name]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def unset_all_attributes
|
|
93
|
+
@attributes = Hash.new
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def set_encoded_attribute(name, value, secret)
|
|
97
|
+
@attributes[name] = encode(value, secret)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def pack
|
|
101
|
+
|
|
102
|
+
attstr = ""
|
|
103
|
+
@attributes.each_pair do |attribute_s, value|
|
|
104
|
+
attribute = @dict.find_attribute_by_name(attribute_s)
|
|
105
|
+
raise "Undefned attribute '#{attribute_s}'." if attribute.nil?
|
|
106
|
+
anum = attribute.id
|
|
107
|
+
val = case attribute.type
|
|
108
|
+
when "string"
|
|
109
|
+
value
|
|
110
|
+
when "integer"
|
|
111
|
+
if attribute.has_values?
|
|
112
|
+
raise "Invalid value name '#{value}'" if attribute.find_values_by_name(value).nil?
|
|
113
|
+
end
|
|
114
|
+
[attribute.has_values? ? attribute.find_values_by_name(value).id : value].pack("N")
|
|
115
|
+
when "ipaddr"
|
|
116
|
+
[inet_aton(value)].pack("N")
|
|
117
|
+
when "date"
|
|
118
|
+
[value].pack("N")
|
|
119
|
+
when "time"
|
|
120
|
+
[value].pack("N")
|
|
121
|
+
else
|
|
122
|
+
next
|
|
123
|
+
end
|
|
124
|
+
attstr += [attribute.id, val.length + 2, val].pack(P_ATTR)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
@packed = [CODES[@code], @id, attstr.length + HDRLEN, @authenticator, attstr].pack(P_HDR)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
protected
|
|
131
|
+
|
|
132
|
+
def unpack
|
|
133
|
+
@code, @id, len, @authenticator, attribute_data = @packed.unpack(P_HDR)
|
|
134
|
+
@code = CODES.index(@code)
|
|
135
|
+
|
|
136
|
+
unset_all_attributes
|
|
137
|
+
|
|
138
|
+
while attribute_data.length > 0 do
|
|
139
|
+
length = attribute_data.unpack("xC").first.to_i
|
|
140
|
+
attribute_type, attribute_value = attribute_data.unpack("Cxa#{length-2}")
|
|
141
|
+
attribute_type = attribute_type.to_i
|
|
142
|
+
|
|
143
|
+
attribute = @dict.find_attribute_by_id(attribute_type)
|
|
144
|
+
attribute_value = case attribute.type
|
|
145
|
+
when 'string'
|
|
146
|
+
attribute_value
|
|
147
|
+
when 'integer'
|
|
148
|
+
attribute.has_values? ? attribute.find_values_by_id(attribute_value.unpack("N")[0]).name : attribute_value.unpack("N")[0]
|
|
149
|
+
when 'ipaddr'
|
|
150
|
+
inet_ntoa(attribute_value.unpack("N")[0])
|
|
151
|
+
when 'time'
|
|
152
|
+
attribute_value.unpack("N")[0]
|
|
153
|
+
when 'date'
|
|
154
|
+
attribute_value.unpack("N")[0]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
set_attribute(attribute.name, attribute_value) if attribute
|
|
158
|
+
attribute_data[0, length] = ""
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def inet_aton(hostname)
|
|
163
|
+
if (hostname =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)
|
|
164
|
+
return (($1.to_i & 0xff) << 24) + (($2.to_i & 0xff) << 16) + (($3.to_i & 0xff) << 8) + (($4.to_i & 0xff))
|
|
165
|
+
end
|
|
166
|
+
0
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def inet_ntoa(iaddr)
|
|
170
|
+
sprintf("%d.%d.%d.%d", (iaddr >> 24) & 0xff, (iaddr >> 16) & 0xff, (iaddr >> 8) & 0xff, (iaddr) & 0xff)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def xor_str(str1, str2)
|
|
174
|
+
i = 0
|
|
175
|
+
newstr = ""
|
|
176
|
+
str1.each_byte do |c1|
|
|
177
|
+
c2 = str2[i]
|
|
178
|
+
newstr = newstr << (c1 ^ c2)
|
|
179
|
+
i = i+1
|
|
180
|
+
end
|
|
181
|
+
newstr
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def encode(value, secret)
|
|
185
|
+
lastround = @authenticator
|
|
186
|
+
encoded_value = ""
|
|
187
|
+
# pad to 16n bytes
|
|
188
|
+
value += "\000" * (15-(15 + value.length) % 16)
|
|
189
|
+
0.step(value.length-1, 16) do |i|
|
|
190
|
+
lastround = xor_str(value[i, 16], Digest::MD5.digest(secret + lastround) )
|
|
191
|
+
encoded_value += lastround
|
|
192
|
+
end
|
|
193
|
+
encoded_value
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def decode(value, secret)
|
|
197
|
+
decoded_value = ""
|
|
198
|
+
lastround = @authenticator
|
|
199
|
+
0.step(value.length-1, 16) do |i|
|
|
200
|
+
decoded_value = xor_str(value[i, 16], Digest::MD5.digest(secret + lastround))
|
|
201
|
+
lastround = value[i, 16]
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
decoded_value.gsub!(/\000+/, "") if decoded_value
|
|
205
|
+
decoded_value[value.length, -1] = "" unless (decoded_value.length <= value.length)
|
|
206
|
+
return decoded_value
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
module Radiustar
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
|
|
5
|
+
class Request
|
|
6
|
+
|
|
7
|
+
def initialize(server, options = {})
|
|
8
|
+
@dict = options[:dict].nil? ? Dictionary.default : options[:dict]
|
|
9
|
+
@nas_ip = options[:nas_ip] || get_my_ip(@host)
|
|
10
|
+
@nas_identifier = options[:nas_identifier] || @nas_ip
|
|
11
|
+
@reply_timeout = options[:reply_timeout].nil? ? 60 : options[:reply_timeout].to_i
|
|
12
|
+
@retries_number = options[:retries_number].nil? ? 1 : options[:retries_number].to_i
|
|
13
|
+
|
|
14
|
+
@host, @port = server.split(":")
|
|
15
|
+
|
|
16
|
+
@port = Socket.getservbyname("radius", "udp") unless @port
|
|
17
|
+
@port = 1812 unless @port
|
|
18
|
+
@port = @port.to_i # just in case
|
|
19
|
+
@socket = UDPSocket.open
|
|
20
|
+
@socket.connect(@host, @port)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def authenticate(name, password, secret, user_attributes = {})
|
|
24
|
+
@packet = Packet.new(@dict, Process.pid & 0xff)
|
|
25
|
+
@packet.gen_auth_authenticator
|
|
26
|
+
@packet.code = 'Access-Request'
|
|
27
|
+
@packet.set_attribute('User-Name', name)
|
|
28
|
+
@packet.set_attribute('NAS-Identifier', @nas_identifier)
|
|
29
|
+
@packet.set_attribute('NAS-IP-Address', @nas_ip)
|
|
30
|
+
@packet.set_encoded_attribute('User-Password', password, secret)
|
|
31
|
+
|
|
32
|
+
user_attributes.each_pair do |name, value|
|
|
33
|
+
@packet.set_attribute(name, value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
send_packet
|
|
38
|
+
@recieved_packet = recv_packet(@reply_timeout)
|
|
39
|
+
rescue Exception => e
|
|
40
|
+
retry if (@retries_number -= 1) > 0
|
|
41
|
+
raise
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
reply = { :code => @recieved_packet.code }
|
|
45
|
+
reply.merge @recieved_packet.attributes
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def accounting_request(status_type, name, secret, sessionid, user_attributes = {})
|
|
49
|
+
|
|
50
|
+
@packet = Packet.new(@dict, Process.pid & 0xff)
|
|
51
|
+
@packet.code = 'Accounting-Request'
|
|
52
|
+
|
|
53
|
+
@packet.set_attribute('User-Name', name)
|
|
54
|
+
@packet.set_attribute('NAS-Identifier', @nas_identifier)
|
|
55
|
+
@packet.set_attribute('NAS-IP-Address', @nas_ip)
|
|
56
|
+
@packet.set_attribute('Acct-Status-Type', status_type)
|
|
57
|
+
@packet.set_attribute('Acct-Session-Id', sessionid)
|
|
58
|
+
@packet.set_attribute('Acct-Authentic', 'RADIUS')
|
|
59
|
+
|
|
60
|
+
user_attributes.each_pair do |name, value|
|
|
61
|
+
@packet.set_attribute(name, value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@packet.gen_acct_authenticator(secret)
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
send_packet
|
|
68
|
+
@recieved_packet = recv_packet(@reply_timeout)
|
|
69
|
+
rescue Exception => e
|
|
70
|
+
retry if (@retries_number -= 1) > 0
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def accounting_start(name, secret, sessionid, options = {})
|
|
78
|
+
accounting_request('Start', name, secret, sessionid, options)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def accounting_update(name, secret, sessionid, options = {})
|
|
82
|
+
accounting_request('Interim-Update', name, secret, sessionid, options)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def accounting_stop(name, secret, sessionid, options = {})
|
|
86
|
+
accounting_request('Stop', name, secret, sessionid, options)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def inspect
|
|
90
|
+
to_s
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def send_packet
|
|
96
|
+
data = @packet.pack
|
|
97
|
+
@packet.increment_id
|
|
98
|
+
@socket.send(data, 0)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def recv_packet(timeout)
|
|
102
|
+
if select([@socket], nil, nil, timeout.to_i) == nil
|
|
103
|
+
raise "Timed out waiting for response packet from server"
|
|
104
|
+
end
|
|
105
|
+
data = @socket.recvfrom(64)
|
|
106
|
+
Packet.new(@dict, Process.pid & 0xff, data[0])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#looks up the source IP address with a route to the specified destination
|
|
110
|
+
def get_my_ip(dest_address)
|
|
111
|
+
orig_reverse_lookup_setting = Socket.do_not_reverse_lookup
|
|
112
|
+
Socket.do_not_reverse_lookup = true
|
|
113
|
+
|
|
114
|
+
UDPSocket.open do |sock|
|
|
115
|
+
sock.connect dest_address, 1
|
|
116
|
+
sock.addr.last
|
|
117
|
+
end
|
|
118
|
+
ensure
|
|
119
|
+
Socket.do_not_reverse_lookup = orig_reverse_lookup_setting
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Radiustar
|
|
2
|
+
|
|
3
|
+
class VendorCollection < Array
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@collection = {}
|
|
7
|
+
@revcollection = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add(id, name)
|
|
11
|
+
@collection[name] ||= Vendor.new(name, id)
|
|
12
|
+
@revcollection[id.to_i] ||= @collection[name]
|
|
13
|
+
self << @collection[name]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find_by_name(name)
|
|
17
|
+
@collection[name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_by_id(id)
|
|
21
|
+
@revcollection[id.to_i]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Vendor
|
|
27
|
+
|
|
28
|
+
include Radiustar
|
|
29
|
+
|
|
30
|
+
attr_reader :name, :id
|
|
31
|
+
|
|
32
|
+
def initialize(name, id)
|
|
33
|
+
@name = name
|
|
34
|
+
@id = id
|
|
35
|
+
@attributes = AttributesCollection.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_attribute(name, id, type)
|
|
39
|
+
@attributes.add(name, id, type)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find_attribute_by_name(name)
|
|
43
|
+
@attributes.find_by_name(name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def find_attribute_by_id(id)
|
|
47
|
+
@attributes.find_by_id(id.to_i)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def has_attributes?
|
|
51
|
+
!@attributes.empty?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def attributes
|
|
55
|
+
@attributes
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
data/radiustar.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = %q{dguerri-radiustar}
|
|
5
|
+
s.version = "0.0.4"
|
|
6
|
+
|
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.authors = ["PJ Davis", "Davide Guerri"]
|
|
9
|
+
s.date = %q{2010-12-04}
|
|
10
|
+
s.description = %q{Ruby Radius Library}
|
|
11
|
+
s.email = %q{davide.guerri@gmail.com}
|
|
12
|
+
s.extra_rdoc_files = ["History.txt", "README.rdoc", "templates/default.txt", "version.txt"]
|
|
13
|
+
s.files = [".gitignore", "History.txt", "README.rdoc", "Rakefile", "lib/radiustar.rb", "lib/radiustar/dictionary.rb", "lib/radiustar/dictionary/attributes.rb", "lib/radiustar/dictionary/values.rb", "lib/radiustar/packet.rb", "lib/radiustar/radiustar.rb", "lib/radiustar/request.rb", "lib/radiustar/vendor.rb", "radiustar.gemspec", "spec/radiustar_spec.rb", "spec/spec_helper.rb", "templates/default.txt", "templates/dictionary.digium", "templates/gandalf.dictionary", "test/test_radiustar.rb", "version.txt"]
|
|
14
|
+
s.homepage = %q{http://github.com/dguerri/radiustar}
|
|
15
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
|
16
|
+
s.require_paths = ["lib"]
|
|
17
|
+
s.rubyforge_project = %q{radiustar}
|
|
18
|
+
s.rubygems_version = %q{1.3.6}
|
|
19
|
+
s.summary = %q{Ruby Radius Library}
|
|
20
|
+
s.test_files = ["test/test_radiustar.rb"]
|
|
21
|
+
|
|
22
|
+
if s.respond_to? :specification_version then
|
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
24
|
+
s.specification_version = 3
|
|
25
|
+
|
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
27
|
+
s.add_development_dependency(%q<bones>, [">= 3.4.1"])
|
|
28
|
+
else
|
|
29
|
+
s.add_dependency(%q<bones>, [">= 3.4.1"])
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
s.add_dependency(%q<bones>, [">= 3.4.1"])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
|
2
|
+
|
|
3
|
+
describe Radiustar::Packet do
|
|
4
|
+
it "gen_authenticator generates a random string without /dev/urandom" do
|
|
5
|
+
File.stub(:exist?).and_return(false)
|
|
6
|
+
packet = Radiustar::Packet.new(nil, nil)
|
|
7
|
+
packet.gen_authenticator.class.should == String
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
if File.exist?("/dev/urandom") # don't fail if specs are running on a platform without /dev/urandom
|
|
11
|
+
it "gen_authenticator generates a random string with /dev/urandom" do
|
|
12
|
+
packet = Radiustar::Packet.new(nil, nil)
|
|
13
|
+
packet.gen_authenticator.class.should == String
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
require File.expand_path(
|
|
3
|
+
File.join(File.dirname(__FILE__), %w[.. lib radiustar]))
|
|
4
|
+
|
|
5
|
+
Spec::Runner.configure do |config|
|
|
6
|
+
# == Mock Framework
|
|
7
|
+
#
|
|
8
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
|
9
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
|
10
|
+
#
|
|
11
|
+
# config.mock_with :mocha
|
|
12
|
+
# config.mock_with :flexmock
|
|
13
|
+
# config.mock_with :rr
|
|
14
|
+
end
|
|
15
|
+
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file contains dictionary translations for parsing
|
|
3
|
+
# requests and generating responses. All transactions are
|
|
4
|
+
# composed of Attribute/Value Pairs. The value of each attribute
|
|
5
|
+
# is specified as one of 4 data types. Valid data types are:
|
|
6
|
+
#
|
|
7
|
+
# string - 0-253 octets
|
|
8
|
+
# ipaddr - 4 octets in network byte order
|
|
9
|
+
# integer - 32 bit value in big endian order (high byte first)
|
|
10
|
+
# date - 32 bit value in big endian order - seconds since
|
|
11
|
+
# 00:00:00 GMT, Jan. 1, 1970
|
|
12
|
+
#
|
|
13
|
+
# Enumerated values are stored in the user file with dictionary
|
|
14
|
+
# VALUE translations for easy administration.
|
|
15
|
+
#
|
|
16
|
+
# Example:
|
|
17
|
+
#
|
|
18
|
+
# ATTRIBUTE VALUE
|
|
19
|
+
# --------------- -----
|
|
20
|
+
# Framed-Protocol = PPP
|
|
21
|
+
# 7 = 1 (integer encoding)
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Proper names for everything - use this instead of the above
|
|
28
|
+
#
|
|
29
|
+
ATTRIBUTE User-Name 1 string
|
|
30
|
+
ATTRIBUTE User-Password 2 string
|
|
31
|
+
ATTRIBUTE CHAP-Password 3 string
|
|
32
|
+
ATTRIBUTE NAS-IP-Address 4 ipaddr
|
|
33
|
+
ATTRIBUTE NAS-Port 5 integer
|
|
34
|
+
ATTRIBUTE Service-Type 6 integer
|
|
35
|
+
ATTRIBUTE Framed-Protocol 7 integer
|
|
36
|
+
ATTRIBUTE Framed-IP-Address 8 ipaddr
|
|
37
|
+
ATTRIBUTE Framed-IP-Netmask 9 ipaddr
|
|
38
|
+
ATTRIBUTE Framed-Routing 10 integer
|
|
39
|
+
ATTRIBUTE Filter-Id 11 string
|
|
40
|
+
ATTRIBUTE Framed-MTU 12 integer
|
|
41
|
+
ATTRIBUTE Framed-Compression 13 integer
|
|
42
|
+
ATTRIBUTE Login-IP-Host 14 ipaddr
|
|
43
|
+
ATTRIBUTE Login-Service 15 integer
|
|
44
|
+
ATTRIBUTE Login-TCP-Port 16 integer
|
|
45
|
+
ATTRIBUTE Reply-Message 18 string
|
|
46
|
+
ATTRIBUTE Callback-Number 19 string
|
|
47
|
+
ATTRIBUTE Callback-Id 20 string
|
|
48
|
+
ATTRIBUTE Expiration 21 date
|
|
49
|
+
ATTRIBUTE Framed-Route 22 string
|
|
50
|
+
ATTRIBUTE Framed-IPX-Network 23 ipaddr
|
|
51
|
+
ATTRIBUTE State 24 string
|
|
52
|
+
ATTRIBUTE Session-Timeout 27 integer
|
|
53
|
+
ATTRIBUTE Idle-Timeout 28 integer
|
|
54
|
+
ATTRIBUTE Termination-Action 29 integer
|
|
55
|
+
ATTRIBUTE Called-Station-Id 30 string
|
|
56
|
+
ATTRIBUTE Calling-Station-Id 31 string
|
|
57
|
+
ATTRIBUTE Acct-Status-Type 40 integer
|
|
58
|
+
ATTRIBUTE Acct-Delay-Time 41 integer
|
|
59
|
+
ATTRIBUTE Acct-Input-Octets 42 integer
|
|
60
|
+
ATTRIBUTE Acct-Output-Octets 43 integer
|
|
61
|
+
ATTRIBUTE Acct-Session-Id 44 string
|
|
62
|
+
ATTRIBUTE Acct-Authentic 45 integer
|
|
63
|
+
ATTRIBUTE Acct-Session-Time 46 integer
|
|
64
|
+
ATTRIBUTE Acct-Terminate-Cause 49 integer
|
|
65
|
+
ATTRIBUTE NAS-Port-Type 61 integer
|
|
66
|
+
ATTRIBUTE Port-Limit 62 integer
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Integer Translations
|
|
71
|
+
#
|
|
72
|
+
|
|
73
|
+
# User Types
|
|
74
|
+
|
|
75
|
+
VALUE Service-Type Login-User 1
|
|
76
|
+
VALUE Service-Type Framed-User 2
|
|
77
|
+
VALUE Service-Type Callback-Login-User 3
|
|
78
|
+
VALUE Service-Type Callback-Framed-User 4
|
|
79
|
+
VALUE Service-Type Outbound-User 5
|
|
80
|
+
VALUE Service-Type Administrative-User 6
|
|
81
|
+
VALUE Service-Type NAS-Prompt-User 7
|
|
82
|
+
|
|
83
|
+
# Framed Protocols
|
|
84
|
+
|
|
85
|
+
VALUE Framed-Protocol PPP 1
|
|
86
|
+
VALUE Framed-Protocol SLIP 2
|
|
87
|
+
|
|
88
|
+
# Framed Routing Values
|
|
89
|
+
|
|
90
|
+
VALUE Framed-Routing None 0
|
|
91
|
+
VALUE Framed-Routing Broadcast 1
|
|
92
|
+
VALUE Framed-Routing Listen 2
|
|
93
|
+
VALUE Framed-Routing Broadcast-Listen 3
|
|
94
|
+
|
|
95
|
+
# Framed Compression Types
|
|
96
|
+
|
|
97
|
+
VALUE Framed-Compression None 0
|
|
98
|
+
VALUE Framed-Compression Van-Jacobson-TCP-IP 1
|
|
99
|
+
|
|
100
|
+
# Login Services
|
|
101
|
+
|
|
102
|
+
VALUE Login-Service Telnet 0
|
|
103
|
+
VALUE Login-Service Rlogin 1
|
|
104
|
+
VALUE Login-Service TCP-Clear 2
|
|
105
|
+
VALUE Login-Service PortMaster 3
|
|
106
|
+
|
|
107
|
+
# Status Types
|
|
108
|
+
|
|
109
|
+
VALUE Acct-Status-Type Start 1
|
|
110
|
+
VALUE Acct-Status-Type Stop 2
|
|
111
|
+
|
|
112
|
+
# Authentication Types
|
|
113
|
+
|
|
114
|
+
VALUE Acct-Authentic RADIUS 1
|
|
115
|
+
VALUE Acct-Authentic Local 2
|
|
116
|
+
VALUE Acct-Authentic PowerLink128 100
|
|
117
|
+
|
|
118
|
+
# Termination Options
|
|
119
|
+
|
|
120
|
+
VALUE Termination-Action Default 0
|
|
121
|
+
VALUE Termination-Action RADIUS-Request 1
|
|
122
|
+
|
|
123
|
+
# NAS Port Types, available in ComOS 3.3.1 and later
|
|
124
|
+
|
|
125
|
+
VALUE NAS-Port-Type Async 0
|
|
126
|
+
VALUE NAS-Port-Type Sync 1
|
|
127
|
+
VALUE NAS-Port-Type ISDN 2
|
|
128
|
+
VALUE NAS-Port-Type ISDN-V120 3
|
|
129
|
+
VALUE NAS-Port-Type ISDN-V110 4
|
|
130
|
+
|
|
131
|
+
# Acct Terminate Causes, available in ComOS 3.3.2 and later
|
|
132
|
+
|
|
133
|
+
VALUE Acct-Terminate-Cause User-Request 1
|
|
134
|
+
VALUE Acct-Terminate-Cause Lost-Carrier 2
|
|
135
|
+
VALUE Acct-Terminate-Cause Lost-Service 3
|
|
136
|
+
VALUE Acct-Terminate-Cause Idle-Timeout 4
|
|
137
|
+
VALUE Acct-Terminate-Cause Session-Timeout 5
|
|
138
|
+
VALUE Acct-Terminate-Cause Admin-Reset 6
|
|
139
|
+
VALUE Acct-Terminate-Cause Admin-Reboot 7
|
|
140
|
+
VALUE Acct-Terminate-Cause Port-Error 8
|
|
141
|
+
VALUE Acct-Terminate-Cause NAS-Error 9
|
|
142
|
+
VALUE Acct-Terminate-Cause NAS-Request 10
|
|
143
|
+
VALUE Acct-Terminate-Cause NAS-Reboot 11
|
|
144
|
+
VALUE Acct-Terminate-Cause Port-Unneeded 12
|
|
145
|
+
VALUE Acct-Terminate-Cause Port-Preempted 13
|
|
146
|
+
VALUE Acct-Terminate-Cause Port-Suspended 14
|
|
147
|
+
VALUE Acct-Terminate-Cause Service-Unavailable 15
|
|
148
|
+
VALUE Acct-Terminate-Cause Callback 16
|
|
149
|
+
VALUE Acct-Terminate-Cause User-Error 17
|
|
150
|
+
VALUE Acct-Terminate-Cause Host-Request 18
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# Obsolete names for backwards compatibility with older users files
|
|
155
|
+
# If you want RADIUS accounting logs to use the new names instead of
|
|
156
|
+
# these, move this section to the beginning of the dictionary file
|
|
157
|
+
# and kill and restart radiusd
|
|
158
|
+
# If you don't have a RADIUS 1.16 users file that you're still using,
|
|
159
|
+
# you can delete or ignore this section.
|
|
160
|
+
#
|
|
161
|
+
ATTRIBUTE Client-Id 4 ipaddr
|
|
162
|
+
ATTRIBUTE Client-Port-Id 5 integer
|
|
163
|
+
ATTRIBUTE User-Service-Type 6 integer
|
|
164
|
+
ATTRIBUTE Framed-Address 8 ipaddr
|
|
165
|
+
ATTRIBUTE Framed-Netmask 9 ipaddr
|
|
166
|
+
ATTRIBUTE Framed-Filter-Id 11 string
|
|
167
|
+
ATTRIBUTE Login-Host 14 ipaddr
|
|
168
|
+
ATTRIBUTE Login-Port 16 integer
|
|
169
|
+
ATTRIBUTE Old-Password 17 string
|
|
170
|
+
ATTRIBUTE Port-Message 18 string
|
|
171
|
+
ATTRIBUTE Dialback-No 19 string
|
|
172
|
+
ATTRIBUTE Dialback-Name 20 string
|
|
173
|
+
ATTRIBUTE Challenge-State 24 string
|
|
174
|
+
VALUE Service-Type Dialback-Login-User 3
|
|
175
|
+
VALUE Service-Type Dialback-Framed-User 4
|
|
176
|
+
VALUE Service-Type Shell-User 6
|
|
177
|
+
VALUE Framed-Compression Van-Jacobsen-TCP-IP 1
|
|
178
|
+
#VALUE Auth-Type Unix 1
|
|
179
|
+
#
|
|
180
|
+
# END of obsolete names for backwards compatibility
|
|
181
|
+
#
|
|
182
|
+
|
|
183
|
+
#
|
|
184
|
+
# Configuration Values
|
|
185
|
+
# uncomment out these two lines to turn account expiration on
|
|
186
|
+
#
|
|
187
|
+
|
|
188
|
+
#VALUE Server-Config Password-Expiration 30
|
|
189
|
+
#VALUE Server-Config Password-Warning 5
|
|
190
|
+
|
|
191
|
+
##
|
|
192
|
+
## VENDOR SPECIFIC ATTRIBUTES
|
|
193
|
+
##
|
|
194
|
+
## The following entries demonstrate the use of VSAs
|
|
195
|
+
##
|
|
196
|
+
|
|
197
|
+
# cisco-avpair is used for various functions by cisco IOS. Most
|
|
198
|
+
# notably, it's used to create VPDN tunnels.
|
|
199
|
+
#
|
|
200
|
+
VENDORATTR 9 cisco-avpair 1 string
|
|
201
|
+
|
|
202
|
+
# This is a fake attribute to demonstrate how to write named-value
|
|
203
|
+
# attributes.
|
|
204
|
+
VENDORATTR 1 ibm-enum 254 integer
|
|
205
|
+
VENDORVALUE 1 ibm-enum value-1 1
|
|
206
|
+
VENDORVALUE 1 ibm-enum value-2 2
|
|
207
|
+
VENDORVALUE 1 ibm-enum value-3 3
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- text -*-
|
|
2
|
+
##############################################################################
|
|
3
|
+
#
|
|
4
|
+
# Digium's Asterisk specific radius attributes
|
|
5
|
+
# markster@digium.com
|
|
6
|
+
#
|
|
7
|
+
# http://bugs.digium.com/file_download.php\?file_id=9688\&type=bug
|
|
8
|
+
#
|
|
9
|
+
# $Id$
|
|
10
|
+
#
|
|
11
|
+
##############################################################################
|
|
12
|
+
|
|
13
|
+
VENDOR Digium 22736
|
|
14
|
+
|
|
15
|
+
BEGIN-VENDOR Digium
|
|
16
|
+
|
|
17
|
+
ATTRIBUTE Asterisk-Acc-Code 101 string
|
|
18
|
+
ATTRIBUTE Asterisk-Src 102 string
|
|
19
|
+
ATTRIBUTE Asterisk-Dst 103 string
|
|
20
|
+
ATTRIBUTE Asterisk-Dst-Ctx 104 string
|
|
21
|
+
ATTRIBUTE Asterisk-Clid 105 string
|
|
22
|
+
ATTRIBUTE Asterisk-Chan 106 string
|
|
23
|
+
ATTRIBUTE Asterisk-Dst-Chan 107 string
|
|
24
|
+
ATTRIBUTE Asterisk-Last-App 108 string
|
|
25
|
+
ATTRIBUTE Asterisk-Last-Data 109 string
|
|
26
|
+
ATTRIBUTE Asterisk-Start-Time 110 string
|
|
27
|
+
ATTRIBUTE Asterisk-Answer-Time 111 string
|
|
28
|
+
ATTRIBUTE Asterisk-End-Time 112 string
|
|
29
|
+
ATTRIBUTE Asterisk-Duration 113 integer
|
|
30
|
+
ATTRIBUTE Asterisk-Bill-Sec 114 integer
|
|
31
|
+
ATTRIBUTE Asterisk-Disposition 115 string
|
|
32
|
+
ATTRIBUTE Asterisk-AMA-Flags 116 string
|
|
33
|
+
ATTRIBUTE Asterisk-Unique-ID 117 string
|
|
34
|
+
ATTRIBUTE Asterisk-User-Field 118 string
|
|
35
|
+
|
|
36
|
+
END-VENDOR Digium
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# -*- text -*-
|
|
2
|
+
# Gandalf dictionary
|
|
3
|
+
#
|
|
4
|
+
# Version: 1.00 24-July-2003 Blaise St-Laurent <bstlaurent@okiok.com>
|
|
5
|
+
#
|
|
6
|
+
# Notes: Dictionary was made specifically for the Gandalf XpressWay
|
|
7
|
+
# RLAN with Link Authentication through RADIUS
|
|
8
|
+
#
|
|
9
|
+
# $Id$
|
|
10
|
+
#
|
|
11
|
+
VENDOR Gandalf 64
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Vendor-specific attributes
|
|
15
|
+
#
|
|
16
|
+
BEGIN-VENDOR Gandalf
|
|
17
|
+
|
|
18
|
+
ATTRIBUTE Gandalf-Remote-LAN-Name 0 string
|
|
19
|
+
ATTRIBUTE Gandalf-Operational-Modes 1 integer
|
|
20
|
+
ATTRIBUTE Gandalf-Compression-Status 2 integer
|
|
21
|
+
ATTRIBUTE Gandalf-Min-Outgoing-Bearer 3 integer
|
|
22
|
+
ATTRIBUTE Gandalf-Authentication-String 5 string
|
|
23
|
+
ATTRIBUTE Gandalf-PPP-Authentication 6 integer
|
|
24
|
+
ATTRIBUTE Gandalf-PPP-NCP-Type 7 integer
|
|
25
|
+
ATTRIBUTE Gandalf-Fwd-Multicast-In 8 integer
|
|
26
|
+
ATTRIBUTE Gandalf-Fwd-Broadcast-In 9 integer
|
|
27
|
+
ATTRIBUTE Gandalf-Fwd-Unicast-In 10 integer
|
|
28
|
+
ATTRIBUTE Gandalf-Fwd-Multicast-Out 11 integer
|
|
29
|
+
ATTRIBUTE Gandalf-Fwd-Broadcast-Out 12 integer
|
|
30
|
+
ATTRIBUTE Gandalf-Fwd-Unicast-Out 13 integer
|
|
31
|
+
ATTRIBUTE Gandalf-Around-The-Corner 14 integer
|
|
32
|
+
ATTRIBUTE Gandalf-Channel-Group-Name-1 15 string
|
|
33
|
+
ATTRIBUTE Gandalf-Dial-Prefix-Name-1 16 string
|
|
34
|
+
ATTRIBUTE Gandalf-Phone-Number-1 17 string
|
|
35
|
+
ATTRIBUTE Gandalf-Calling-Line-ID-1 18 string
|
|
36
|
+
ATTRIBUTE Gandalf-Channel-Group-Name-2 19 string
|
|
37
|
+
ATTRIBUTE Gandalf-Dial-Prefix-Name-2 20 string
|
|
38
|
+
ATTRIBUTE Gandalf-Phone-Number-2 21 string
|
|
39
|
+
ATTRIBUTE Gandalf-Calling-Line-ID-2 22 string
|
|
40
|
+
ATTRIBUTE Gandalf-IPX-Spoofing-State 23 integer
|
|
41
|
+
ATTRIBUTE Gandalf-IPX-Watchdog-Spoof 24 integer
|
|
42
|
+
ATTRIBUTE Gandalf-SAP-Group-Name-1 25 string
|
|
43
|
+
ATTRIBUTE Gandalf-SAP-Group-Name-2 26 string
|
|
44
|
+
ATTRIBUTE Gandalf-SAP-Group-Name-3 27 string
|
|
45
|
+
ATTRIBUTE Gandalf-SAP-Group-Name-4 28 string
|
|
46
|
+
ATTRIBUTE Gandalf-SAP-Group-Name-5 29 string
|
|
47
|
+
ATTRIBUTE Gandalf-Hunt-Group 30 string
|
|
48
|
+
ATTRIBUTE Gandalf-Modem-Mode 31 integer
|
|
49
|
+
ATTRIBUTE Gandalf-Modem-Required-1 32 integer
|
|
50
|
+
ATTRIBUTE Gandalf-Modem-Required-2 33 integer
|
|
51
|
+
|
|
52
|
+
VALUE Gandalf-Operational-Modes Disabled 1
|
|
53
|
+
VALUE Gandalf-Operational-Modes Called-Only 2
|
|
54
|
+
VALUE Gandalf-Operational-Modes Calling-Called 3
|
|
55
|
+
VALUE Gandalf-Operational-Modes Calling-Only 4
|
|
56
|
+
|
|
57
|
+
VALUE Gandalf-Compression-Status Disabled 1
|
|
58
|
+
VALUE Gandalf-Compression-Status Enabled 2
|
|
59
|
+
|
|
60
|
+
VALUE Gandalf-Min-Outgoing-Bearer Unrestricted-64K 1
|
|
61
|
+
VALUE Gandalf-Min-Outgoing-Bearer Digital-56K 2
|
|
62
|
+
VALUE Gandalf-Min-Outgoing-Bearer 3100Hz-Audio 3
|
|
63
|
+
|
|
64
|
+
VALUE Gandalf-PPP-Authentication CHAP 1
|
|
65
|
+
VALUE Gandalf-PPP-Authentication PAP 2
|
|
66
|
+
VALUE Gandalf-PPP-Authentication PAP-Sending-on-Incoming-Calls 3
|
|
67
|
+
|
|
68
|
+
VALUE Gandalf-PPP-NCP-Type BCP 2
|
|
69
|
+
VALUE Gandalf-PPP-NCP-Type IPCP 3
|
|
70
|
+
|
|
71
|
+
VALUE Gandalf-Fwd-Multicast-In Disabled 1
|
|
72
|
+
VALUE Gandalf-Fwd-Multicast-In Enabled 2
|
|
73
|
+
|
|
74
|
+
VALUE Gandalf-Fwd-Broadcast-In Disabled 1
|
|
75
|
+
VALUE Gandalf-Fwd-Broadcast-In Enabled 2
|
|
76
|
+
|
|
77
|
+
VALUE Gandalf-Fwd-Unicast-In Disabled 1
|
|
78
|
+
VALUE Gandalf-Fwd-Unicast-In Enabled 2
|
|
79
|
+
|
|
80
|
+
VALUE Gandalf-Fwd-Multicast-Out Disabled 1
|
|
81
|
+
VALUE Gandalf-Fwd-Multicast-Out Enabled 2
|
|
82
|
+
|
|
83
|
+
VALUE Gandalf-Fwd-Broadcast-Out Disabled 1
|
|
84
|
+
VALUE Gandalf-Fwd-Broadcast-Out Enabled 2
|
|
85
|
+
|
|
86
|
+
VALUE Gandalf-Fwd-Unicast-Out Disabled 1
|
|
87
|
+
VALUE Gandalf-Fwd-Unicast-Out Enabled 2
|
|
88
|
+
|
|
89
|
+
VALUE Gandalf-IPX-Spoofing-State Forward 1
|
|
90
|
+
VALUE Gandalf-IPX-Spoofing-State Spoof 2
|
|
91
|
+
VALUE Gandalf-IPX-Spoofing-State Filter-all-outgoing-RIP-SAP 3
|
|
92
|
+
|
|
93
|
+
VALUE Gandalf-IPX-Watchdog-Spoof Disabled 1
|
|
94
|
+
VALUE Gandalf-IPX-Watchdog-Spoof Enabled 2
|
|
95
|
+
|
|
96
|
+
VALUE Gandalf-Modem-Mode Disabled 1
|
|
97
|
+
VALUE Gandalf-Modem-Mode Enabled 2
|
|
98
|
+
|
|
99
|
+
VALUE Gandalf-Modem-Required-1 Disabled 1
|
|
100
|
+
VALUE Gandalf-Modem-Required-1 Enabled 2
|
|
101
|
+
|
|
102
|
+
VALUE Gandalf-Modem-Required-2 Disabled 1
|
|
103
|
+
VALUE Gandalf-Modem-Required-2 Enabled 2
|
|
104
|
+
|
|
105
|
+
END-VENDOR Gandalf
|
|
File without changes
|
data/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.4
|
metadata
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dguerri-radiustar
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 4
|
|
10
|
+
version: 0.0.4
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- PJ Davis
|
|
14
|
+
- Davide Guerri
|
|
15
|
+
autorequire:
|
|
16
|
+
bindir: bin
|
|
17
|
+
cert_chain: []
|
|
18
|
+
|
|
19
|
+
date: 2010-12-04 00:00:00 +01:00
|
|
20
|
+
default_executable:
|
|
21
|
+
dependencies:
|
|
22
|
+
- !ruby/object:Gem::Dependency
|
|
23
|
+
name: bones
|
|
24
|
+
prerelease: false
|
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
26
|
+
none: false
|
|
27
|
+
requirements:
|
|
28
|
+
- - ">="
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
hash: 21
|
|
31
|
+
segments:
|
|
32
|
+
- 3
|
|
33
|
+
- 4
|
|
34
|
+
- 1
|
|
35
|
+
version: 3.4.1
|
|
36
|
+
type: :development
|
|
37
|
+
version_requirements: *id001
|
|
38
|
+
description: Ruby Radius Library
|
|
39
|
+
email: davide.guerri@gmail.com
|
|
40
|
+
executables: []
|
|
41
|
+
|
|
42
|
+
extensions: []
|
|
43
|
+
|
|
44
|
+
extra_rdoc_files:
|
|
45
|
+
- History.txt
|
|
46
|
+
- README.rdoc
|
|
47
|
+
- templates/default.txt
|
|
48
|
+
- version.txt
|
|
49
|
+
files:
|
|
50
|
+
- .gitignore
|
|
51
|
+
- History.txt
|
|
52
|
+
- README.rdoc
|
|
53
|
+
- Rakefile
|
|
54
|
+
- lib/radiustar.rb
|
|
55
|
+
- lib/radiustar/dictionary.rb
|
|
56
|
+
- lib/radiustar/dictionary/attributes.rb
|
|
57
|
+
- lib/radiustar/dictionary/values.rb
|
|
58
|
+
- lib/radiustar/packet.rb
|
|
59
|
+
- lib/radiustar/radiustar.rb
|
|
60
|
+
- lib/radiustar/request.rb
|
|
61
|
+
- lib/radiustar/vendor.rb
|
|
62
|
+
- radiustar.gemspec
|
|
63
|
+
- spec/radiustar_spec.rb
|
|
64
|
+
- spec/spec_helper.rb
|
|
65
|
+
- templates/default.txt
|
|
66
|
+
- templates/dictionary.digium
|
|
67
|
+
- templates/gandalf.dictionary
|
|
68
|
+
- test/test_radiustar.rb
|
|
69
|
+
- version.txt
|
|
70
|
+
has_rdoc: true
|
|
71
|
+
homepage: http://github.com/dguerri/radiustar
|
|
72
|
+
licenses: []
|
|
73
|
+
|
|
74
|
+
post_install_message:
|
|
75
|
+
rdoc_options:
|
|
76
|
+
- --main
|
|
77
|
+
- README.rdoc
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
hash: 3
|
|
86
|
+
segments:
|
|
87
|
+
- 0
|
|
88
|
+
version: "0"
|
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
|
+
none: false
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
hash: 3
|
|
95
|
+
segments:
|
|
96
|
+
- 0
|
|
97
|
+
version: "0"
|
|
98
|
+
requirements: []
|
|
99
|
+
|
|
100
|
+
rubyforge_project: radiustar
|
|
101
|
+
rubygems_version: 1.3.7
|
|
102
|
+
signing_key:
|
|
103
|
+
specification_version: 3
|
|
104
|
+
summary: Ruby Radius Library
|
|
105
|
+
test_files:
|
|
106
|
+
- test/test_radiustar.rb
|