radiustar 0.0.3 → 0.0.5

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.
@@ -1,3 +1,14 @@
1
+ == 0.0.5 / 2012-02-02
2
+ * Bugfixes(charl, Mark Bryars, jamesotron, dguerri, gderosa, mkocher)
3
+ * You can now use VSAs in packets (jamesotron)
4
+ * Add support for CoA and Disconnect Packets (Mark Bryars)
5
+
6
+ == 0.0.4 / 2010-12/04
7
+ * github fork (dguerri)
8
+ * bugfixes
9
+ * added multiple dictionary files support (dguerri)
10
+ * added accounting support
11
+
1
12
  == 0.0.3 / 2010-06-30
2
13
  * no longer require to specify your own IP (via bwlang)
3
14
 
@@ -32,27 +32,50 @@ Ruby Radius Library
32
32
 
33
33
  == FEATURES
34
34
 
35
- * Import your own radius dictionary
36
- * Test authentication
37
- * Check radius replies
38
-
39
- == COMING SOON
40
-
41
- * Use vendor specific attributes
35
+ * Import your own radius dictionaries
36
+ * Authentication
37
+ * Accounting
42
38
 
43
39
  == SYNOPSIS:
44
40
 
41
+ require 'rubygems'
45
42
  require 'radiustar'
46
43
 
47
- req = Radiustar::Request.new('server')
48
- resp = req.authenticate('my_user', 'my_password', 'shared_secret')
49
- resp #=> true
50
-
51
- attrs = req.get_attributes('my_user', 'my_password', 'shared_secret')
52
- attrs #=> ["Access-Accept", {"Service-Type"=>"Framed-User",
53
- "Framed-Protocol"=>"PPP",
54
- "Framed-IP-Address"=>"255.255.255.254",
55
- "Framed-IP-Netmask"=>"255.255.255.255"} ]
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
56
79
 
57
80
  == REQUIREMENTS:
58
81
 
@@ -62,6 +85,17 @@ Ruby Radius Library
62
85
 
63
86
  gem install radiustar
64
87
 
88
+ == Thanks:
89
+ Thanks to everyone who has contributed to this project. Without your help and support, this would not have been possible.
90
+
91
+ * charl
92
+ * Mark Bryars
93
+ * jamesotron
94
+ * dguerri
95
+ * gderosa
96
+ * mkocher
97
+ * bwlang
98
+
65
99
  == LICENSE:
66
100
 
67
101
  Copyright (c) 2010 [PJ Davis], released under the CC0 1.0 Universal license.
data/Rakefile CHANGED
@@ -9,12 +9,12 @@ task :default => 'test:run'
9
9
  task 'gem:release' => 'test:run'
10
10
 
11
11
  Bones {
12
- name 'radiustar'
13
- authors 'PJ Davis'
14
- email 'pj.davis@gmail.com'
15
- url 'http://github.com/pjdavis/radiustar'
16
- ignore_file '.gitignore'
17
- readme_file 'README.rdoc'
18
-
12
+ name 'radiustar'
13
+ authors 'PJ Davis'
14
+ email 'pj.davis@gmail.com'
15
+ url 'http://github.com/pjdavis/radiustar'
16
+ ignore_file '.gitignore'
17
+ readme_file 'README.rdoc'
18
+ depend_on 'ipaddr_extensions'
19
19
  }
20
20
 
@@ -4,38 +4,25 @@ module Radiustar
4
4
 
5
5
  include Radiustar
6
6
 
7
- DEFAULT_DICTIONARY_PATH = ::File.join(::File.dirname(__FILE__), '..', '..', 'templates', 'default.txt')
7
+ DEFAULT_DICTIONARIES_PATH = ::File.join(::File.dirname(__FILE__), '..', '..', 'templates')
8
8
 
9
9
  def initialize(initial_path = nil)
10
10
  @attributes = AttributesCollection.new
11
11
  @vendors = VendorCollection.new
12
12
 
13
- read initial_path if initial_path
13
+ read_files initial_path if initial_path
14
14
  end
15
15
 
16
- def read(path)
17
- file = File.open(path) do |f|
18
- current_vendor = nil
19
- f.each_line do |line|
20
- next if line =~ /^\#/ # discard comments
21
- split_line = line.split(/\s+/)
22
- next if split_line == []
23
- case split_line.first.upcase
24
- when "ATTRIBUTE"
25
- current_vendor.nil? ? set_attr(split_line) : set_vendor_attr(current_vendor, split_line)
26
- when "VALUE"
27
- current_vendor.nil? ? set_value(split_line) : set_vendor_value(current_vendor, split_line)
28
- when "VENDOR"
29
- add_vendor(split_line)
30
- when "BEGIN-VENDOR"
31
- current_vendor = set_vendor(split_line)
32
- when "END-VENDOR"
33
- current_vendor = nil
34
- end
35
- end
36
- end
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
+ }
37
24
  end
38
-
25
+
39
26
  def find_attribute_by_name(name)
40
27
  @attributes.find_by_name(name)
41
28
  end
@@ -67,9 +54,59 @@ module Radiustar
67
54
  class << self
68
55
 
69
56
  def default
70
- new DEFAULT_DICTIONARY_PATH
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
71
82
  end
83
+ end
72
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
73
110
  end
74
111
 
75
112
  private
@@ -100,4 +137,4 @@ module Radiustar
100
137
 
101
138
  end
102
139
 
103
- end
140
+ end
@@ -2,13 +2,20 @@ module Radiustar
2
2
 
3
3
  class AttributesCollection < Array
4
4
 
5
- def initialize
5
+ attr_accessor :vendor
6
+
7
+ def initialize vendor=nil
6
8
  @collection = {}
7
- @revcollection = []
9
+ @revcollection = {}
10
+ @vendor = vendor if vendor
8
11
  end
9
12
 
10
13
  def add(name, id, type)
11
- @collection[name] ||= Attribute.new(name, id.to_i, type)
14
+ if vendor?
15
+ @collection[name] ||= Attribute.new(name, id.to_i, type, @vendor)
16
+ else
17
+ @collection[name] ||= Attribute.new(name, id.to_i, type)
18
+ end
12
19
  @revcollection[id.to_i] ||= @collection[name]
13
20
  self << @collection[name]
14
21
  end
@@ -18,7 +25,11 @@ module Radiustar
18
25
  end
19
26
 
20
27
  def find_by_id(id)
21
- @revcollection[id.to_i]
28
+ @revcollection[id]
29
+ end
30
+
31
+ def vendor?
32
+ !!@vendor
22
33
  end
23
34
 
24
35
  end
@@ -27,13 +38,14 @@ module Radiustar
27
38
 
28
39
  include Radiustar
29
40
 
30
- attr_reader :name, :id, :type
41
+ attr_reader :name, :id, :type, :vendor
31
42
 
32
- def initialize(name, id, type)
43
+ def initialize(name, id, type, vendor=nil)
33
44
  @values = ValuesCollection.new
34
45
  @name = name
35
46
  @id = id.to_i
36
47
  @type = type
48
+ @vendor = vendor if vendor
37
49
  end
38
50
 
39
51
  def add_value(name, id)
@@ -56,6 +68,10 @@ module Radiustar
56
68
  @values
57
69
  end
58
70
 
71
+ def vendor?
72
+ !!@vendor
73
+ end
74
+
59
75
  end
60
76
 
61
- end
77
+ end
@@ -4,7 +4,7 @@ module Radiustar
4
4
 
5
5
  def initialize
6
6
  @collection = {}
7
- @revcollection = []
7
+ @revcollection = {}
8
8
  end
9
9
 
10
10
  def add(name, id)
@@ -31,7 +31,7 @@ module Radiustar
31
31
 
32
32
  include Radiustar
33
33
 
34
- attr_accessor :name
34
+ attr_accessor :name, :id
35
35
 
36
36
  def initialize(name, id)
37
37
  @name = name
@@ -40,4 +40,4 @@ module Radiustar
40
40
 
41
41
  end
42
42
 
43
- end
43
+ end
@@ -1,13 +1,17 @@
1
1
  module Radiustar
2
2
 
3
3
  require 'digest/md5'
4
+ require 'ipaddr_extensions'
4
5
 
5
6
  class Packet
6
7
 
7
8
  CODES = { 'Access-Request' => 1, 'Access-Accept' => 2,
8
9
  'Access-Reject' => 3, 'Accounting-Request' => 4,
9
10
  'Accounting-Response' => 5, 'Access-Challenge' => 11,
10
- 'Status-Server' => 12, 'Status-Client' => 13 }
11
+ 'Status-Server' => 12, 'Status-Client' => 13,
12
+ 'Disconnect-Request' => 40, 'Disconnect-ACK' => 41,
13
+ 'Disconnect-NAK' => 42, 'CoA-Request' => 43,
14
+ 'CoA-ACK' => 44, 'CoA-NAK' => 45 }
11
15
 
12
16
 
13
17
  HDRLEN = 1 + 1 + 2 + 16 # size of packet header
@@ -15,7 +19,7 @@ module Radiustar
15
19
  P_ATTR = "CCa*" # pack template for attribute
16
20
 
17
21
  attr_accessor :code
18
- attr_reader :id, :attributes
22
+ attr_reader :id, :attributes, :authenticator
19
23
 
20
24
  def initialize(dictionary, id, data = nil)
21
25
  @dict = dictionary
@@ -38,63 +42,100 @@ module Radiustar
38
42
 
39
43
  # Generate an authenticator. It will try to use /dev/urandom if
40
44
  # possible, or the system rand call if that's not available.
41
- def gen_authenticator
42
- authenticator = []
43
- 8.times do
44
- authenticator << rand(65536)
45
- end
46
- authenticator.pack("n8")
45
+ def gen_auth_authenticator
47
46
  if (File.exist?("/dev/urandom"))
48
47
  File.open("/dev/urandom") do |urandom|
49
- authenticator = urandom.read(16)
48
+ @authenticator = urandom.read(16)
49
+ end
50
+ else
51
+ @authenticator = []
52
+ 8.times do
53
+ @authenticator << rand(65536)
50
54
  end
55
+ @authenticator = @authenticator.pack("n8")
56
+ end
57
+ end
58
+
59
+ def gen_acct_authenticator(secret)
60
+ # From RFC2866
61
+ # Request Authenticator
62
+ #
63
+ # In Accounting-Request Packets, the Authenticator value is a 16
64
+ # octet MD5 [5] checksum, called the Request Authenticator.
65
+ #
66
+ # The NAS and RADIUS accounting server share a secret. The Request
67
+ # Authenticator field in Accounting-Request packets contains a one-
68
+ # way MD5 hash calculated over a stream of octets consisting of the
69
+ # Code + Identifier + Length + 16 zero octets + request attributes +
70
+ # shared secret (where + indicates concatenation). The 16 octet MD5
71
+ # hash value is stored in the Authenticator field of the
72
+ # Accounting-Request packet.
73
+ #
74
+ # Note that the Request Authenticator of an Accounting-Request can
75
+ # not be done the same way as the Request Authenticator of a RADIUS
76
+ # Access-Request, because there is no User-Password attribute in an
77
+ # Accounting-Request.
78
+ #
79
+ @authenticator = "\000"*16
80
+ @authenticator = Digest::MD5.digest(pack + secret)
81
+ @packed = nil
82
+ @authenticator
83
+ end
84
+
85
+ def gen_response_authenticator(secret, request_authenticator)
86
+ @authenticator = request_authenticator
87
+ @authenticator = Digest::MD5.digest(pack + secret)
88
+ @packed = nil
89
+ @authenticator
90
+ end
91
+
92
+ def validate_acct_authenticator(secret)
93
+ if @authenticator
94
+ original_authenticator = @authenticator
95
+ if gen_acct_authenticator(secret) == original_authenticator
96
+ true
97
+ else
98
+ @authenticator = original_authenticator
99
+ false
100
+ end
101
+ else
102
+ false
51
103
  end
52
- @authenticator = authenticator
53
104
  end
54
105
 
55
106
  def set_attribute(name, value)
56
- @attributes[name] = value
107
+ @attributes[name] = Attribute.new(@dict, name, value)
57
108
  end
58
109
 
59
110
  def unset_attribute(name)
60
- @attributes[name] = nil
111
+ @attributes.delete(name)
61
112
  end
62
113
 
63
114
  def attribute(name)
64
- @attributes[name]
115
+ if @attributes[name]
116
+ @attributes[name].value
117
+ end
65
118
  end
66
119
 
67
120
  def unset_all_attributes
68
- @attributes = Hash.new
121
+ @attributes = {}
69
122
  end
70
123
 
71
124
  def set_encoded_attribute(name, value, secret)
72
- @attributes[name] = encode(value, secret)
125
+ @attributes[name] = Attribute.new(@dict, name, encode(value, secret))
73
126
  end
74
127
 
75
- def pack
128
+ def decode_attribute(name, secret)
129
+ if @attributes[name]
130
+ decode(@attributes[name].value.to_s, secret)
131
+ end
132
+ end
76
133
 
134
+ def pack
77
135
  attstr = ""
78
- @attributes.each_pair do |attribute, value|
79
- attribute = @dict.find_attribute_by_name(attribute)
80
- anum = attribute.id
81
- val = case attribute.type
82
- when "string"
83
- value
84
- when "integer"
85
- [attribute.has_values? ? attribute.find_values_by_id(value) : value].pack("N")
86
- when "ipaddr"
87
- [inet_aton(value)].pack("N")
88
- when "date"
89
- [value].pack("N")
90
- when "time"
91
- [value].pack("N")
92
- else
93
- next
94
- end
95
- attstr += [attribute.id, val.length + 2, val].pack(P_ATTR)
136
+ @attributes.values.each do |attribute|
137
+ attstr += attribute.pack
96
138
  end
97
-
98
139
  @packed = [CODES[@code], @id, attstr.length + HDRLEN, @authenticator, attstr].pack(P_HDR)
99
140
  end
100
141
 
@@ -102,7 +143,10 @@ module Radiustar
102
143
 
103
144
  def unpack
104
145
  @code, @id, len, @authenticator, attribute_data = @packed.unpack(P_HDR)
105
- @code = CODES.index(@code)
146
+ raise "Incomplete Packet(read #{@packed.length} != #{len})" if @packed.length != len
147
+
148
+ @code = CODES.key(@code)
149
+ vendor = nil
106
150
 
107
151
  unset_all_attributes
108
152
 
@@ -111,41 +155,43 @@ module Radiustar
111
155
  attribute_type, attribute_value = attribute_data.unpack("Cxa#{length-2}")
112
156
  attribute_type = attribute_type.to_i
113
157
 
114
- attribute = @dict.find_attribute_by_id(attribute_type)
115
- attribute_value = case attribute.class
158
+ if attribute_type == 26 # Vendor Specific Attribute
159
+ vid, attribute_type, attribute_value = attribute_data.unpack("xxNCxa#{length-6}")
160
+ vendor = @dict.vendors.find_by_id(vid)
161
+ attribute = vendor.find_attribute_by_id(attribute_type)
162
+ else
163
+ vendor = nil
164
+ attribute = @dict.find_attribute_by_id(attribute_type)
165
+ end
166
+
167
+ attribute_value = case attribute.type
116
168
  when 'string'
117
169
  attribute_value
118
170
  when 'integer'
119
171
  attribute.has_values? ? attribute.find_values_by_id(attribute_value.unpack("N")[0]).name : attribute_value.unpack("N")[0]
120
172
  when 'ipaddr'
121
- inet_ntoa(attribute_value.unpack("N")[0])
173
+ attribute_value.unpack("N")[0].to_ip.to_s
122
174
  when 'time'
123
175
  attribute_value.unpack("N")[0]
124
176
  when 'date'
125
177
  attribute_value.unpack("N")[0]
126
178
  end
127
179
 
128
- set_attribute(attribute.name, attribute_value) if attribute
129
- attribute_data[0, length] = ""
130
- end
131
- end
180
+ if vendor
181
+ set_attribute(vendor.name+"/"+attribute.name, attribute_value) if attribute
182
+ else
183
+ set_attribute(attribute.name, attribute_value) if attribute
184
+ end
132
185
 
133
- def inet_aton(hostname)
134
- if (hostname =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)
135
- return (($1.to_i & 0xff) << 24) + (($2.to_i & 0xff) << 16) + (($3.to_i & 0xff) << 8) + (($4.to_i & 0xff))
186
+ attribute_data[0, length] = ""
136
187
  end
137
- 0
138
- end
139
-
140
- def inet_ntoa(iaddr)
141
- sprintf("%d.%d.%d.%d", (iaddr >> 24) & 0xff, (iaddr >> 16) & 0xff, (iaddr >> 8) & 0xff, (iaddr) & 0xff)
142
188
  end
143
189
 
144
190
  def xor_str(str1, str2)
145
191
  i = 0
146
192
  newstr = ""
147
193
  str1.each_byte do |c1|
148
- c2 = str2[i]
194
+ c2 = str2.bytes.to_a[i]
149
195
  newstr = newstr << (c1 ^ c2)
150
196
  i = i+1
151
197
  end
@@ -168,7 +214,7 @@ module Radiustar
168
214
  decoded_value = ""
169
215
  lastround = @authenticator
170
216
  0.step(value.length-1, 16) do |i|
171
- decoded_value = xor_str(value[i, 16], Digest::MD5.digest(secret + lastround))
217
+ decoded_value += xor_str(value[i, 16], Digest::MD5.digest(secret + lastround))
172
218
  lastround = value[i, 16]
173
219
  end
174
220
 
@@ -177,5 +223,92 @@ module Radiustar
177
223
  return decoded_value
178
224
  end
179
225
 
226
+ class Attribute
227
+
228
+ attr_reader :dict, :name, :vendor
229
+ attr_accessor :value
230
+
231
+ def initialize dict, name, value, vendor=nil
232
+ @dict = dict
233
+ # This is the cheapest and easiest way to add VSA's!
234
+ if (name && (chunks = name.split('/')) && (chunks.size == 2))
235
+ @vendor = chunks[0]
236
+ @name = chunks[1]
237
+ else
238
+ @name = name
239
+ end
240
+ @vendor ||= vendor
241
+ @value = value.is_a?(Attribute) ? value.to_s : value
242
+ end
243
+
244
+ def vendor?
245
+ !!@vendor
246
+ end
247
+
248
+ def pack
249
+ attribute = if (vendor? && (@dict.vendors.find_by_name(@vendor)))
250
+ @dict.vendors.find_by_name(@vendor).attributes.find_by_name(@name)
251
+ else
252
+ @dict.find_attribute_by_name(@name)
253
+ end
254
+ raise "Undefined attribute '#{@name}'." if attribute.nil?
255
+
256
+ if vendor?
257
+ pack_vendor_specific_attribute attribute
258
+ else
259
+ pack_attribute attribute
260
+ end
261
+ end
262
+
263
+ def inspect
264
+ @value
265
+ end
266
+
267
+ def to_s
268
+ @value
269
+ end
270
+
271
+ private
272
+
273
+ def pack_vendor_specific_attribute attribute
274
+ inside_attribute = pack_attribute attribute
275
+ vid = attribute.vendor.id.to_i
276
+ header = [ 26, inside_attribute.size + 6 ].pack("CC") # 26: Type = Vendor-Specific, 4: length of Vendor-Id field
277
+ header += [ 0, vid >> 16, vid >> 8, vid ].pack("CCCC") # first byte of Vendor-Id is 0
278
+ header + inside_attribute
279
+ end
280
+
281
+ def pack_attribute attribute
282
+ anum = attribute.id
283
+ val = case attribute.type
284
+ when "string"
285
+ @value
286
+ when "integer"
287
+ raise "Invalid value name '#{@value}'." if attribute.has_values? && attribute.find_values_by_name(@value).nil?
288
+ [attribute.has_values? ? attribute.find_values_by_name(@value).id : @value].pack("N")
289
+ when "ipaddr"
290
+ [@value.to_ip.to_i].pack("N")
291
+ when "ipv6addr"
292
+ ipi = @value.to_ip.to_i
293
+ [ ipi >> 96, ipi >> 64, ipi >> 32, ipi ].pack("NNNN")
294
+ when "date"
295
+ [@value].pack("N")
296
+ when "time"
297
+ [@value].pack("N")
298
+ else
299
+ ""
300
+ end
301
+ begin
302
+ [anum,
303
+ val.length + 2,
304
+ val
305
+ ].pack(P_ATTR)
306
+ rescue
307
+ puts "#{@name} => #{@value}"
308
+ puts [anum, val.length + 2, val].inspect
309
+ end
310
+ end
311
+
312
+ end
180
313
  end
181
- end
314
+ end
@@ -4,12 +4,14 @@ module Radiustar
4
4
 
5
5
  class Request
6
6
 
7
- def initialize(server, my_ip = nil, dict_file = nil)
8
- @dict = dict_file.nil? ? Dictionary.default : Dictionary.new(dict_file)
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
9
13
 
10
14
  @host, @port = server.split(":")
11
-
12
- @my_ip = my_ip || get_my_ip(@host)
13
15
 
14
16
  @port = Socket.getservbyname("radius", "udp") unless @port
15
17
  @port = 1812 unless @port
@@ -18,29 +20,104 @@ module Radiustar
18
20
  @socket.connect(@host, @port)
19
21
  end
20
22
 
21
- def authenticate(name, password, secret)
23
+ def authenticate(name, password, secret, user_attributes = {})
22
24
  @packet = Packet.new(@dict, Process.pid & 0xff)
25
+ @packet.gen_auth_authenticator
23
26
  @packet.code = 'Access-Request'
24
- @packet.gen_authenticator
25
27
  @packet.set_attribute('User-Name', name)
26
- @packet.set_attribute('NAS-IP-Address', @my_ip)
28
+ @packet.set_attribute('NAS-Identifier', @nas_identifier)
29
+ @packet.set_attribute('NAS-IP-Address', @nas_ip)
27
30
  @packet.set_encoded_attribute('User-Password', password, secret)
28
- send_packet
29
- @recieved_packet = recv_packet
30
- return @recieved_packet.code == 'Access-Accept'
31
+
32
+ user_attributes.each_pair do |name, value|
33
+ @packet.set_attribute(name, value)
34
+ end
35
+
36
+ retries = @retries_number
37
+ begin
38
+ send_packet
39
+ @received_packet = recv_packet(@reply_timeout)
40
+ rescue Exception => e
41
+ retry if (retries -= 1) > 0
42
+ raise
43
+ end
44
+
45
+ reply = { :code => @received_packet.code }
46
+ reply.merge @received_packet.attributes
31
47
  end
48
+
49
+ def accounting_request(status_type, name, secret, sessionid, user_attributes = {})
32
50
 
33
- def get_attributes(name, password, secret)
34
51
  @packet = Packet.new(@dict, Process.pid & 0xff)
35
- @packet.code = 'Access-Request'
36
- @packet.gen_authenticator
52
+ @packet.code = 'Accounting-Request'
53
+
37
54
  @packet.set_attribute('User-Name', name)
38
- @packet.set_attribute('NAS-IP-Address', @my_ip)
39
- @packet.set_encoded_attribute('User-Password', password, secret)
40
- send_packet
41
- @recieved_packet = recv_packet
42
- recieved_thing = [@recieved_packet.code]
43
- recieved_thing << @recieved_packet.attributes
55
+ @packet.set_attribute('NAS-Identifier', @nas_identifier)
56
+ @packet.set_attribute('NAS-IP-Address', @nas_ip)
57
+ @packet.set_attribute('Acct-Status-Type', status_type)
58
+ @packet.set_attribute('Acct-Session-Id', sessionid)
59
+ @packet.set_attribute('Acct-Authentic', 'RADIUS')
60
+
61
+ user_attributes.each_pair do |name, value|
62
+ @packet.set_attribute(name, value)
63
+ end
64
+
65
+ @packet.gen_acct_authenticator(secret)
66
+
67
+ retries = @retries_number
68
+ begin
69
+ send_packet
70
+ @received_packet = recv_packet(@reply_timeout)
71
+ rescue Exception => e
72
+ retry if (retries -= 1) > 0
73
+ raise
74
+ end
75
+
76
+ return true
77
+ end
78
+
79
+ def generic_request(code, secret, user_attributes = {})
80
+ @packet = Packet.new(@dict, Process.pid & 0xff)
81
+ @packet.code = code
82
+ @packet.set_attribute('NAS-Identifier', @nas_identifier)
83
+ @packet.set_attribute('NAS-IP-Address', @nas_ip)
84
+
85
+ user_attributes.each_pair do |name, value|
86
+ @packet.set_attribute(name, value)
87
+ end
88
+
89
+ @packet.gen_acct_authenticator(secret)
90
+
91
+ retries = @retries_number
92
+ begin
93
+ send_packet
94
+ @received_packet = recv_packet(@reply_timeout)
95
+ rescue Exception => e
96
+ retry if (retries -= 1) > 0
97
+ raise
98
+ end
99
+
100
+ return true
101
+ end
102
+
103
+ def coa_request(secret, user_attributes = {})
104
+ generic_request('CoA-Request', secret, user_attributes)
105
+ end
106
+
107
+ def disconnect_request(secret, user_attributes = {})
108
+ generic_request('Disconnect-Request', secret, user_attributes)
109
+ end
110
+
111
+ def accounting_start(name, secret, sessionid, options = {})
112
+ accounting_request('Start', name, secret, sessionid, options)
113
+ end
114
+
115
+ def accounting_update(name, secret, sessionid, options = {})
116
+ accounting_request('Interim-Update', name, secret, sessionid, options)
117
+ end
118
+
119
+ def accounting_stop(name, secret, sessionid, options = {})
120
+ accounting_request('Stop', name, secret, sessionid, options)
44
121
  end
45
122
 
46
123
  def inspect
@@ -51,15 +128,14 @@ module Radiustar
51
128
 
52
129
  def send_packet
53
130
  data = @packet.pack
54
- @packet.increment_id
55
131
  @socket.send(data, 0)
56
132
  end
57
133
 
58
- def recv_packet
59
- if select([@socket], nil, nil, 60) == nil
60
- raise "Timed out waiting for response packet from server"
134
+ def recv_packet(timeout)
135
+ if select([@socket], nil, nil, timeout.to_i) == nil
136
+ raise "Timed out waiting for response packet from server"
61
137
  end
62
- data = @socket.recvfrom(64)
138
+ data = @socket.recvfrom(4096) # rfc2865 max packet length
63
139
  Packet.new(@dict, Process.pid & 0xff, data[0])
64
140
  end
65
141
 
@@ -78,4 +154,4 @@ module Radiustar
78
154
 
79
155
  end
80
156
 
81
- end
157
+ end
@@ -32,7 +32,7 @@ module Radiustar
32
32
  def initialize(name, id)
33
33
  @name = name
34
34
  @id = id
35
- @attributes = AttributesCollection.new
35
+ @attributes = AttributesCollection.new self
36
36
  end
37
37
 
38
38
  def add_attribute(name, id, type)
@@ -57,4 +57,4 @@ module Radiustar
57
57
 
58
58
  end
59
59
 
60
- end
60
+ end
@@ -1,34 +1,36 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
3
  Gem::Specification.new do |s|
4
- s.name = %q{radiustar}
5
- s.version = "0.0.3"
4
+ s.name = "radiustar"
5
+ s.version = "0.0.5"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["PJ Davis"]
9
- s.date = %q{2010-06-30}
10
- s.description = %q{Ruby Radius Library}
11
- s.email = %q{pj.davis@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/pjdavis/radiustar}
9
+ s.date = "2012-02-02"
10
+ s.description = "Ruby Radius Library"
11
+ s.email = "pj.davis@gmail.com"
12
+ s.extra_rdoc_files = ["History.txt", "README.rdoc", "templates/default.txt"]
13
+ s.files = ["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", "spec/value_spec.rb", "templates/default.txt", "templates/dictionary.digium", "templates/dictionary.rfc2865", "templates/gandalf.dictionary", "test/test_radiustar.rb", "version.txt"]
14
+ s.homepage = "http://github.com/pjdavis/radiustar"
15
15
  s.rdoc_options = ["--main", "README.rdoc"]
16
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}
17
+ s.rubyforge_project = "radiustar"
18
+ s.rubygems_version = "1.8.15"
19
+ s.summary = "."
20
20
  s.test_files = ["test/test_radiustar.rb"]
21
21
 
22
22
  if s.respond_to? :specification_version then
23
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
23
  s.specification_version = 3
25
24
 
26
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
- s.add_development_dependency(%q<bones>, [">= 3.4.1"])
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<ipaddr_extensions>, [">= 2012.1.13"])
27
+ s.add_development_dependency(%q<bones>, [">= 3.7.3"])
28
28
  else
29
- s.add_dependency(%q<bones>, [">= 3.4.1"])
29
+ s.add_dependency(%q<ipaddr_extensions>, [">= 2012.1.13"])
30
+ s.add_dependency(%q<bones>, [">= 3.7.3"])
30
31
  end
31
32
  else
32
- s.add_dependency(%q<bones>, [">= 3.4.1"])
33
+ s.add_dependency(%q<ipaddr_extensions>, [">= 2012.1.13"])
34
+ s.add_dependency(%q<bones>, [">= 3.7.3"])
33
35
  end
34
36
  end
@@ -1,6 +1,16 @@
1
-
2
1
  require File.join(File.dirname(__FILE__), %w[spec_helper])
3
2
 
4
- describe Radiustar do
5
- end
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
6
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
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ describe Radiustar::Value do
4
+ it "should get numeric value of NAS-Port-Type == Ethernet from dictionary.rfc2865" do
5
+ dict = Radiustar::Dictionary.new
6
+ dict.read(File.dirname(__FILE__) + '/../templates/dictionary.rfc2865')
7
+ attribute = dict.find_attribute_by_name 'NAS-Port-Type'
8
+ ethernet_value = attribute.find_values_by_name 'Ethernet'
9
+ ethernet_value.id.should == 15
10
+ end
11
+ end
@@ -0,0 +1,137 @@
1
+ # -*- text -*-
2
+ #
3
+ # Attributes and values defined in RFC 2865.
4
+ # http://www.ietf.org/rfc/rfc2865.txt
5
+ #
6
+ # $Id$
7
+ #
8
+ ATTRIBUTE User-Name 1 string
9
+ ATTRIBUTE User-Password 2 string encrypt=1
10
+ ATTRIBUTE CHAP-Password 3 octets
11
+ ATTRIBUTE NAS-IP-Address 4 ipaddr
12
+ ATTRIBUTE NAS-Port 5 integer
13
+ ATTRIBUTE Service-Type 6 integer
14
+ ATTRIBUTE Framed-Protocol 7 integer
15
+ ATTRIBUTE Framed-IP-Address 8 ipaddr
16
+ ATTRIBUTE Framed-IP-Netmask 9 ipaddr
17
+ ATTRIBUTE Framed-Routing 10 integer
18
+ ATTRIBUTE Filter-Id 11 string
19
+ ATTRIBUTE Framed-MTU 12 integer
20
+ ATTRIBUTE Framed-Compression 13 integer
21
+ ATTRIBUTE Login-IP-Host 14 ipaddr
22
+ ATTRIBUTE Login-Service 15 integer
23
+ ATTRIBUTE Login-TCP-Port 16 integer
24
+ # Attribute 17 is undefined
25
+ ATTRIBUTE Reply-Message 18 string
26
+ ATTRIBUTE Callback-Number 19 string
27
+ ATTRIBUTE Callback-Id 20 string
28
+ # Attribute 21 is undefined
29
+ ATTRIBUTE Framed-Route 22 string
30
+ ATTRIBUTE Framed-IPX-Network 23 ipaddr
31
+ ATTRIBUTE State 24 octets
32
+ ATTRIBUTE Class 25 octets
33
+ ATTRIBUTE Vendor-Specific 26 octets
34
+ ATTRIBUTE Session-Timeout 27 integer
35
+ ATTRIBUTE Idle-Timeout 28 integer
36
+ ATTRIBUTE Termination-Action 29 integer
37
+ ATTRIBUTE Called-Station-Id 30 string
38
+ ATTRIBUTE Calling-Station-Id 31 string
39
+ ATTRIBUTE NAS-Identifier 32 string
40
+ ATTRIBUTE Proxy-State 33 octets
41
+ ATTRIBUTE Login-LAT-Service 34 string
42
+ ATTRIBUTE Login-LAT-Node 35 string
43
+ ATTRIBUTE Login-LAT-Group 36 octets
44
+ ATTRIBUTE Framed-AppleTalk-Link 37 integer
45
+ ATTRIBUTE Framed-AppleTalk-Network 38 integer
46
+ ATTRIBUTE Framed-AppleTalk-Zone 39 string
47
+
48
+ ATTRIBUTE CHAP-Challenge 60 octets
49
+ ATTRIBUTE NAS-Port-Type 61 integer
50
+ ATTRIBUTE Port-Limit 62 integer
51
+ ATTRIBUTE Login-LAT-Port 63 string
52
+
53
+ #
54
+ # Integer Translations
55
+ #
56
+
57
+ # Service types
58
+
59
+ VALUE Service-Type Login-User 1
60
+ VALUE Service-Type Framed-User 2
61
+ VALUE Service-Type Callback-Login-User 3
62
+ VALUE Service-Type Callback-Framed-User 4
63
+ VALUE Service-Type Outbound-User 5
64
+ VALUE Service-Type Administrative-User 6
65
+ VALUE Service-Type NAS-Prompt-User 7
66
+ VALUE Service-Type Authenticate-Only 8
67
+ VALUE Service-Type Callback-NAS-Prompt 9
68
+ VALUE Service-Type Call-Check 10
69
+ VALUE Service-Type Callback-Administrative 11
70
+
71
+ # Framed Protocols
72
+
73
+ VALUE Framed-Protocol PPP 1
74
+ VALUE Framed-Protocol SLIP 2
75
+ VALUE Framed-Protocol ARAP 3
76
+ VALUE Framed-Protocol Gandalf-SLML 4
77
+ VALUE Framed-Protocol Xylogics-IPX-SLIP 5
78
+ VALUE Framed-Protocol X.75-Synchronous 6
79
+
80
+ # Framed Routing Values
81
+
82
+ VALUE Framed-Routing None 0
83
+ VALUE Framed-Routing Broadcast 1
84
+ VALUE Framed-Routing Listen 2
85
+ VALUE Framed-Routing Broadcast-Listen 3
86
+
87
+ # Framed Compression Types
88
+
89
+ VALUE Framed-Compression None 0
90
+ VALUE Framed-Compression Van-Jacobson-TCP-IP 1
91
+ VALUE Framed-Compression IPX-Header-Compression 2
92
+ VALUE Framed-Compression Stac-LZS 3
93
+
94
+ # Login Services
95
+
96
+ VALUE Login-Service Telnet 0
97
+ VALUE Login-Service Rlogin 1
98
+ VALUE Login-Service TCP-Clear 2
99
+ VALUE Login-Service PortMaster 3
100
+ VALUE Login-Service LAT 4
101
+ VALUE Login-Service X25-PAD 5
102
+ VALUE Login-Service X25-T3POS 6
103
+ VALUE Login-Service TCP-Clear-Quiet 8
104
+
105
+ # Login-TCP-Port (see /etc/services for more examples)
106
+
107
+ VALUE Login-TCP-Port Telnet 23
108
+ VALUE Login-TCP-Port Rlogin 513
109
+ VALUE Login-TCP-Port Rsh 514
110
+
111
+ # Termination Options
112
+
113
+ VALUE Termination-Action Default 0
114
+ VALUE Termination-Action RADIUS-Request 1
115
+
116
+ # NAS Port Types
117
+
118
+ VALUE NAS-Port-Type Async 0
119
+ VALUE NAS-Port-Type Sync 1
120
+ VALUE NAS-Port-Type ISDN 2
121
+ VALUE NAS-Port-Type ISDN-V120 3
122
+ VALUE NAS-Port-Type ISDN-V110 4
123
+ VALUE NAS-Port-Type Virtual 5
124
+ VALUE NAS-Port-Type PIAFS 6
125
+ VALUE NAS-Port-Type HDLC-Clear-Channel 7
126
+ VALUE NAS-Port-Type X.25 8
127
+ VALUE NAS-Port-Type X.75 9
128
+ VALUE NAS-Port-Type G.3-Fax 10
129
+ VALUE NAS-Port-Type SDSL 11
130
+ VALUE NAS-Port-Type ADSL-CAP 12
131
+ VALUE NAS-Port-Type ADSL-DMT 13
132
+ VALUE NAS-Port-Type IDSL 14
133
+ VALUE NAS-Port-Type Ethernet 15
134
+ VALUE NAS-Port-Type xDSL 16
135
+ VALUE NAS-Port-Type Cable 17
136
+ VALUE NAS-Port-Type Wireless-Other 18
137
+ VALUE NAS-Port-Type Wireless-802.11 19
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.5
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radiustar
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 21
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
8
  - 0
8
- - 3
9
- version: 0.0.3
9
+ - 5
10
+ version: 0.0.5
10
11
  platform: ruby
11
12
  authors:
12
13
  - PJ Davis
@@ -14,23 +15,40 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-06-30 00:00:00 -05:00
18
- default_executable:
18
+ date: 2012-02-02 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: bones
21
+ name: ipaddr_extensions
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
24
25
  requirements:
25
26
  - - ">="
26
27
  - !ruby/object:Gem::Version
28
+ hash: 16097
27
29
  segments:
28
- - 3
29
- - 4
30
+ - 2012
30
31
  - 1
31
- version: 3.4.1
32
- type: :development
32
+ - 13
33
+ version: 2012.1.13
34
+ type: :runtime
33
35
  version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: bones
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 29
45
+ segments:
46
+ - 3
47
+ - 7
48
+ - 3
49
+ version: 3.7.3
50
+ type: :development
51
+ version_requirements: *id002
34
52
  description: Ruby Radius Library
35
53
  email: pj.davis@gmail.com
36
54
  executables: []
@@ -41,9 +59,7 @@ extra_rdoc_files:
41
59
  - History.txt
42
60
  - README.rdoc
43
61
  - templates/default.txt
44
- - version.txt
45
62
  files:
46
- - .gitignore
47
63
  - History.txt
48
64
  - README.rdoc
49
65
  - Rakefile
@@ -58,12 +74,13 @@ files:
58
74
  - radiustar.gemspec
59
75
  - spec/radiustar_spec.rb
60
76
  - spec/spec_helper.rb
77
+ - spec/value_spec.rb
61
78
  - templates/default.txt
62
79
  - templates/dictionary.digium
80
+ - templates/dictionary.rfc2865
63
81
  - templates/gandalf.dictionary
64
82
  - test/test_radiustar.rb
65
83
  - version.txt
66
- has_rdoc: true
67
84
  homepage: http://github.com/pjdavis/radiustar
68
85
  licenses: []
69
86
 
@@ -74,25 +91,29 @@ rdoc_options:
74
91
  require_paths:
75
92
  - lib
76
93
  required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
77
95
  requirements:
78
96
  - - ">="
79
97
  - !ruby/object:Gem::Version
98
+ hash: 3
80
99
  segments:
81
100
  - 0
82
101
  version: "0"
83
102
  required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
84
104
  requirements:
85
105
  - - ">="
86
106
  - !ruby/object:Gem::Version
107
+ hash: 3
87
108
  segments:
88
109
  - 0
89
110
  version: "0"
90
111
  requirements: []
91
112
 
92
113
  rubyforge_project: radiustar
93
- rubygems_version: 1.3.6
114
+ rubygems_version: 1.8.15
94
115
  signing_key:
95
116
  specification_version: 3
96
- summary: Ruby Radius Library
117
+ summary: .
97
118
  test_files:
98
119
  - test/test_radiustar.rb
data/.gitignore DELETED
@@ -1,7 +0,0 @@
1
- .DS_Store
2
- announcement.txt
3
- coverage
4
- doc
5
- pkg
6
- *.gem
7
- **/.DS_Store