radiustar 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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