ruby-radius 1.1
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/README +37 -0
- data/dictionary +289 -0
- data/examples/radiusclient.rb +51 -0
- data/examples/rclient.rb +24 -0
- data/lib/radius/auth.rb +131 -0
- data/lib/radius/dictionary.rb +350 -0
- data/lib/radius/packet.rb +504 -0
- data/test/test.pl +45 -0
- data/test/test.rb +34 -0
- metadata +53 -0
@@ -0,0 +1,350 @@
|
|
1
|
+
# Radius Dictionary file reader
|
2
|
+
# Copyright (C) 2002 Rafael R. Sevilla <dido@imperium.ph>
|
3
|
+
# This file is part of the Radius Authentication Module for Ruby
|
4
|
+
#
|
5
|
+
# The Radius Authentication Module for Ruby is free software; you can
|
6
|
+
# redistribute it and/or modify it under the terms of the GNU Lesser
|
7
|
+
# General Public License as published by the Free Software
|
8
|
+
# Foundation; either version 2.1 of the License, or (at your option)
|
9
|
+
# any later version.
|
10
|
+
#
|
11
|
+
# The Radius Authentication Module is distributed in the hope that it
|
12
|
+
# will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
13
|
+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
14
|
+
# See the GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
17
|
+
# License along with the GNU C Library; if not, write to the Free
|
18
|
+
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
19
|
+
# 02111-1307 USA.
|
20
|
+
#
|
21
|
+
# Author:: Rafael R. Sevilla (mailto:dido@imperium.ph)
|
22
|
+
# Copyright:: Copyright (c) 2002 Rafael R. Sevilla
|
23
|
+
# License:: GNU Lesser General Public License
|
24
|
+
# $Id: dictionary.rb 2 2006-12-17 06:16:21Z dido $
|
25
|
+
#
|
26
|
+
|
27
|
+
module Radius
|
28
|
+
# This is a simple class that can read RADIUS dictionary files and
|
29
|
+
# parse them, allowing conversion between dictionary names and
|
30
|
+
# numbers. Vendor-specific attributes are supported in a way
|
31
|
+
# consistent with the standards.
|
32
|
+
#
|
33
|
+
# This class is patterned after the Net::Radius::Dictionary Perl
|
34
|
+
# module written by Christopher Masto (mailto:chris@netmonger.net)
|
35
|
+
# and Luis E. Munoz (mailto:lem@cantv.net)
|
36
|
+
class Dictionary
|
37
|
+
# Initialize all the instance variables. All variables
|
38
|
+
# start out as empty versions of the appropriate type.
|
39
|
+
def initialize(dictionary_path = nil)
|
40
|
+
@attr = Hash.new(nil)
|
41
|
+
@rattr = Array.new
|
42
|
+
@val = Array.new
|
43
|
+
@rval = Array.new
|
44
|
+
@vsattr = Array.new
|
45
|
+
@rvsattr = Array.new
|
46
|
+
@vsaval = Array.new
|
47
|
+
@rvsaval = Array.new
|
48
|
+
dictionary_path = File.dirname(__FILE__) + '/../../dictionary' unless dictionary_path
|
49
|
+
File.open(dictionary_path, 'r') {|f| read(f)}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Parse a dictionary file from an IO object and learn the
|
53
|
+
# name<->number mappings from it. Only the /first/ definition
|
54
|
+
# will apply if multiple definitions are seen. This method may be
|
55
|
+
# called multiple times with different IO objects, reading from
|
56
|
+
# several files.
|
57
|
+
#
|
58
|
+
# ====Parameters
|
59
|
+
#
|
60
|
+
# +fp+:: IO object from which to read the data
|
61
|
+
#
|
62
|
+
# ====Return
|
63
|
+
# None. Any strangeness in the file results in a message being
|
64
|
+
# printed to stderr.
|
65
|
+
def read(fp)
|
66
|
+
fp.each_line {
|
67
|
+
|line|
|
68
|
+
next if line =~ /^\#/ # discard comments
|
69
|
+
next if (sl = line.split(/\s+/)) == []
|
70
|
+
case sl[0].upcase
|
71
|
+
when "ATTRIBUTE"
|
72
|
+
@attr[sl[1]] = [sl[2].to_i, sl[3]] if (@attr[sl[1]] == nil)
|
73
|
+
@rattr[sl[2].to_i] = [sl[1], sl[3]] if (@rattr[sl[2].to_i] == nil)
|
74
|
+
when "VALUE"
|
75
|
+
if (@attr[sl[1]] == nil)
|
76
|
+
$stderr.print("Warning: value given for unknown attribute #{sl[1]}");
|
77
|
+
else
|
78
|
+
if (@val[@attr[sl[1]][0]] == nil)
|
79
|
+
@val[@attr[sl[1]][0]] = {}
|
80
|
+
end
|
81
|
+
if (@rval[@attr[sl[1]][0]] == nil)
|
82
|
+
@rval[@attr[sl[1]][0]] = []
|
83
|
+
end
|
84
|
+
if (@val[@attr[sl[1]][0]][sl[2]] == nil)
|
85
|
+
@val[@attr[sl[1]][0]][sl[2]] = sl[3].to_i
|
86
|
+
end
|
87
|
+
if (@rval[@attr[sl[1]][0]][sl[3].to_i] == nil)
|
88
|
+
@rval[@attr[sl[1]][0]][sl[3].to_i] = sl[2]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
when "VENDORATTR"
|
92
|
+
sl[3] = Kernel::Integer(sl[3]) # this gets hex and octal
|
93
|
+
# values correctly
|
94
|
+
@vsattr[sl[1].to_i] = {} if (@vsattr[sl[1].to_i] == nil)
|
95
|
+
@rvsattr[sl[1].to_i] = {} if (@rvsattr[sl[1].to_i] == nil)
|
96
|
+
|
97
|
+
if (@vsattr[sl[1].to_i][sl[2]] == nil)
|
98
|
+
@vsattr[sl[1].to_i][sl[2]] = sl[3..4]
|
99
|
+
end
|
100
|
+
|
101
|
+
if (@rvsattr[sl[1].to_i][sl[3]] == nil)
|
102
|
+
@rvsattr[sl[1].to_i][sl[3]] = [sl[2], sl[4]]
|
103
|
+
end
|
104
|
+
when "VENDORVALUE"
|
105
|
+
sl[4] = Kernel::Integer(sl[4])
|
106
|
+
if (@vsattr[sl[1].to_i][sl[2]] == nil)
|
107
|
+
$stderr.print "Warning: vendor value for unknown vendor attribute #{sl[1]} found - ignored\n"
|
108
|
+
else
|
109
|
+
sl[1] = sl[1].to_i
|
110
|
+
@vsaval[sl[1]] = {} if @vsaval[sl[1].to_i] == nil
|
111
|
+
@rvsaval[sl[1]] = {} if @rvsaval[sl[1].to_i] == nil
|
112
|
+
if @vsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]] == nil
|
113
|
+
@vsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]] = {}
|
114
|
+
end
|
115
|
+
|
116
|
+
if @rvsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]] == nil
|
117
|
+
@rvsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]] = []
|
118
|
+
end
|
119
|
+
|
120
|
+
if @vsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]][sl[3]] == nil
|
121
|
+
@vsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]][sl[3]] = sl[4]
|
122
|
+
end
|
123
|
+
|
124
|
+
if @rvsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]][sl[4]] == nil
|
125
|
+
@rvsaval[sl[1]][@vsattr[sl[1]][sl[2]][0]][sl[4]] = sl[3]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
else
|
129
|
+
$stderr.print "Warning: Weird dictionary line: #{line}\n"
|
130
|
+
end
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Given an attribute name, return the number corresponding to it,
|
135
|
+
# based on the dictionary file(s) that have been read.
|
136
|
+
#
|
137
|
+
# ====Parameters
|
138
|
+
# +attrname+:: Name of the attribute whose number is desired.
|
139
|
+
#
|
140
|
+
# ====Return Value
|
141
|
+
# The number corresponding to the appropriate attribute name
|
142
|
+
# given.
|
143
|
+
def attr_num(attrname)
|
144
|
+
if (@attr[attrname] == nil || @attr[attrname][0] == nil)
|
145
|
+
return(nil)
|
146
|
+
end
|
147
|
+
return(@attr[attrname][0])
|
148
|
+
end
|
149
|
+
|
150
|
+
# Given an attribute name, return the corresponding type for it,
|
151
|
+
# based on the dictionary file(s) that have been read.
|
152
|
+
#
|
153
|
+
# ====Parameters
|
154
|
+
# +attrname+:: Name of the attribute whose type is desired.
|
155
|
+
#
|
156
|
+
# ====Return Value
|
157
|
+
# The type string corresponding to the appropriate attribute name
|
158
|
+
# given. This is either string, ipaddr, integer, or date.
|
159
|
+
def attr_type(attrname)
|
160
|
+
if (@attr[attrname] == nil || @attr[attrname][1] == nil)
|
161
|
+
return(nil)
|
162
|
+
end
|
163
|
+
return(@attr[attrname][1])
|
164
|
+
end
|
165
|
+
|
166
|
+
# Given an attribute number, return the name corresponding to it,
|
167
|
+
# based on the dictionary file(s) that have been read, the reverse
|
168
|
+
# of the attr_num method.
|
169
|
+
#
|
170
|
+
# ====Parameters
|
171
|
+
# +attrname+:: Name of the attribute whose number is desired.
|
172
|
+
#
|
173
|
+
# ====Return Value
|
174
|
+
# The number corresponding to the appropriate attribute name
|
175
|
+
# given.
|
176
|
+
def attr_name(attrnum)
|
177
|
+
if (@rattr[attrnum] == nil || @rattr[attrnum][0] == nil)
|
178
|
+
return(nil)
|
179
|
+
end
|
180
|
+
return(@rattr[attrnum][0])
|
181
|
+
end
|
182
|
+
|
183
|
+
# Given an attribute number, return the type of the attribute
|
184
|
+
# corresponding to it, based on the dictionary file(s) that have
|
185
|
+
# been read.
|
186
|
+
#
|
187
|
+
# ====Parameters
|
188
|
+
# +attrnum+:: Number of the attribute whose type is desired.
|
189
|
+
#
|
190
|
+
# ====Return Value
|
191
|
+
# The number corresponding to the appropriate attribute name
|
192
|
+
# given.
|
193
|
+
def attr_numtype(attrnum)
|
194
|
+
if (@rattr[attrnum] == nil || @rattr[attrnum][1] == nil)
|
195
|
+
return(nil)
|
196
|
+
end
|
197
|
+
return(@rattr[attrnum][1])
|
198
|
+
end
|
199
|
+
|
200
|
+
# Given an attribute number, return true or false depending on
|
201
|
+
# whether or not a value has been given for it.
|
202
|
+
#
|
203
|
+
# ====Parameters
|
204
|
+
# +attrnum+:: Number of the attribute whose definition is known
|
205
|
+
#
|
206
|
+
# ====Return Value
|
207
|
+
# True or false depending on whether some value has been given to
|
208
|
+
# the attribute
|
209
|
+
def attr_has_val(attrnum)
|
210
|
+
return(@val[attrnum] != nil)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Alias for attr_has_val. Don't use this; it's confusing.
|
214
|
+
#
|
215
|
+
# ====Parameters
|
216
|
+
# +attrname+:: Name of the attribute whose number is desired.
|
217
|
+
#
|
218
|
+
# ====Return Value
|
219
|
+
# The number corresponding to the appropriate attribute name
|
220
|
+
# given.
|
221
|
+
def val_has_name(attrnum)
|
222
|
+
return(@rval[attrnum] != nil)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Give the number of the named value for the attribute number
|
226
|
+
# supplied.
|
227
|
+
#
|
228
|
+
# ====Parameters
|
229
|
+
# +attrnum+:: the attribute number
|
230
|
+
# +valname+:: the name of the value
|
231
|
+
# ====Return
|
232
|
+
# The number of the named value and attribute.
|
233
|
+
def val_num(attrnum, valname)
|
234
|
+
return(@val[attrnum][valname])
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns the name of the numbered value for the attribute value
|
238
|
+
# supplied. The reverse of val_num.
|
239
|
+
#
|
240
|
+
# ====Parameters
|
241
|
+
# +attrnum+:: the attribute number
|
242
|
+
# +valname+:: the name of the value
|
243
|
+
# ====Return
|
244
|
+
# The name of the numbered value and attribute.
|
245
|
+
def val_name(attrnum, valnum)
|
246
|
+
return(@rval[attrnum][valnum])
|
247
|
+
end
|
248
|
+
|
249
|
+
# Obtains the code of a vendor-specific attribute given the
|
250
|
+
# Vendor-Id and the name of the vendor-specific attribute (e.g. 9
|
251
|
+
# for Cisco and 'cisco-avpair').
|
252
|
+
#
|
253
|
+
# =====Parameters
|
254
|
+
# +vendorid+:: the Vendor-Id
|
255
|
+
# +name+:: the name of the vendor-specific attribute to query
|
256
|
+
# =====Return Value
|
257
|
+
# The code for the vendor-specific attribute
|
258
|
+
def vsattr_num(vendorid, name)
|
259
|
+
return(@vsattr[vendorid][name][0])
|
260
|
+
end
|
261
|
+
|
262
|
+
# Obtains the type of a vendor-specific attribute given the
|
263
|
+
# Vendor-Id and the name of the vendor-specific attribute.
|
264
|
+
#
|
265
|
+
# =====Parameters
|
266
|
+
# +vendorid+:: the Vendor-Id
|
267
|
+
# +name+:: the name of the vendor-specific attribute to query
|
268
|
+
# =====Return Value
|
269
|
+
# The type for the vendor-specific attribute
|
270
|
+
def vsattr_type(vendorid, name)
|
271
|
+
return(@vsattr[vendorid][name][1])
|
272
|
+
end
|
273
|
+
|
274
|
+
# Obtains the name of a vendor-specific attribute given the
|
275
|
+
# Vendor-Id and the code of the vendor-specific attribute. The
|
276
|
+
# inverse of vsattr_num.
|
277
|
+
#
|
278
|
+
# =====Parameters
|
279
|
+
# +vendorid+:: the Vendor-Id
|
280
|
+
# +code+:: the code of the vendor-specific attribute to query
|
281
|
+
# =====Return Value
|
282
|
+
# The name of the vendor-specific attribute
|
283
|
+
def vsattr_name(vendorid, code)
|
284
|
+
return(@rvsattr[vendorid][code][0])
|
285
|
+
end
|
286
|
+
|
287
|
+
# Obtains the type of a vendor-specific attribute given the
|
288
|
+
# Vendor-Id and the code of the vendor-specific attribute.
|
289
|
+
#
|
290
|
+
# =====Parameters
|
291
|
+
# +vendorid+:: the Vendor-Id
|
292
|
+
# +code+:: the code of the vendor-specific attribute to query
|
293
|
+
# =====Return Value
|
294
|
+
# The type of the vendor-specific attribute
|
295
|
+
def vsattr_numtype(vendorid, code)
|
296
|
+
return(@rvsattr[vendorid][code][1]) rescue nil
|
297
|
+
end
|
298
|
+
|
299
|
+
# Determines whether the vendor-specific attibute with the
|
300
|
+
# Vendor-Id and code given has a value.
|
301
|
+
# =====Parameters
|
302
|
+
# +vendorid+:: the Vendor-Id
|
303
|
+
# +code+:: the code of the vendor-specific attribute to query
|
304
|
+
# =====Return Value
|
305
|
+
# True or false on whether or not the vendor-specific attribute
|
306
|
+
# has a value
|
307
|
+
def vsattr_has_val(vendorid, code)
|
308
|
+
return(@vsaval[vendorid][code] != nil)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Alias for vsattr_has_val. Don't use this; it's confusing.
|
312
|
+
#
|
313
|
+
# ====Parameters
|
314
|
+
# +vendorid+:: the Vendor-Id
|
315
|
+
# +attrnum+:: Name of the attribute whose number is desired.
|
316
|
+
#
|
317
|
+
# ====Return Value
|
318
|
+
# The number corresponding to the appropriate attribute name
|
319
|
+
# given.
|
320
|
+
def vsaval_has_name(vendorid, attrnum)
|
321
|
+
return(@rvsaval[vendorid][attrnum] != nil)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Give the number of the named value for the vendor-specific
|
325
|
+
# attribute number supplied.
|
326
|
+
#
|
327
|
+
# ====Parameters
|
328
|
+
# +vendorid+:: the Vendor-Id
|
329
|
+
# +attrnum+:: the attribute number
|
330
|
+
# +valname+:: the name of the value
|
331
|
+
# ====Return
|
332
|
+
# The number of the named value and attribute.
|
333
|
+
def vsaval_num(vendorid, attrnum, valname)
|
334
|
+
return(@vsaval[vendorid][attrnum][valname])
|
335
|
+
end
|
336
|
+
|
337
|
+
# Returns the name of the numbered value for the vendor-specific
|
338
|
+
# attribute value supplied. The reverse of val_num.
|
339
|
+
#
|
340
|
+
# ====Parameters
|
341
|
+
# +vendorid+:: the vendor ID
|
342
|
+
# +attrnum+:: the attribute number
|
343
|
+
# +valname+:: the name of the value
|
344
|
+
# ====Return
|
345
|
+
# The name of the numbered value and attribute.
|
346
|
+
def vsaval_name(vendorid, attrnum, valnum)
|
347
|
+
return(@rvsaval[vendorid][attrnum][valnum])
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
@@ -0,0 +1,504 @@
|
|
1
|
+
# Radius Authentication Module for Ruby
|
2
|
+
# Copyright (C) 2002 Rafael R. Sevilla <dido@imperium.ph>
|
3
|
+
# This file is part of the Radius Authentication Module for Ruby
|
4
|
+
#
|
5
|
+
# The Radius Authentication Module for Ruby is free software; you can
|
6
|
+
# redistribute it and/or modify it under the terms of the GNU Lesser
|
7
|
+
# General Public License as published by the Free Software
|
8
|
+
# Foundation; either version 2.1 of the License, or (at your option)
|
9
|
+
# any later version.
|
10
|
+
#
|
11
|
+
# The Radius Authentication Module is distributed in the hope that it
|
12
|
+
# will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
13
|
+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
14
|
+
# See the GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public
|
17
|
+
# License along with the GNU C Library; if not, write to the Free
|
18
|
+
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
19
|
+
# 02111-1307 USA.
|
20
|
+
#
|
21
|
+
# Author:: Rafael R. Sevilla (mailto:dido@imperium.ph)
|
22
|
+
# Copyright:: Copyright (c) 2002 Rafael R. Sevilla
|
23
|
+
# License:: GNU Lesser General Public License
|
24
|
+
#
|
25
|
+
# $Id: packet.rb 2 2006-12-17 06:16:21Z dido $
|
26
|
+
#
|
27
|
+
|
28
|
+
module Radius
|
29
|
+
# RADIUS (RFC 2138) specifies a binary packet format which contains
|
30
|
+
# various values and attributes. This class provides an interface
|
31
|
+
# to turn RADIUS packets into Ruby data structures and vice-versa.
|
32
|
+
#
|
33
|
+
# Note that the Radius::Packet module does <em>not</em> provide
|
34
|
+
# methods for obtaining or transmitting RADIUS packets to and from
|
35
|
+
# the network. A client of this module must provide that for
|
36
|
+
# himself or herself.
|
37
|
+
#
|
38
|
+
# This class is patterned after the Net::Radius::Packet Perl
|
39
|
+
# module written by Christopher Masto (mailto:chris@netmonger.net).
|
40
|
+
require 'digest/md5'
|
41
|
+
require 'radius/dictionary'
|
42
|
+
|
43
|
+
class Packet
|
44
|
+
# The code field is returned as a string. As of this writing, the
|
45
|
+
# following codes are recognized:
|
46
|
+
#
|
47
|
+
# Access-Request Access-Accept
|
48
|
+
# Access-Reject Accounting-Request
|
49
|
+
# Accounting-Response Access-Challenge
|
50
|
+
# Status-Server Status-Client
|
51
|
+
attr_reader :code
|
52
|
+
|
53
|
+
# The code may be set to any of the strings described above in the
|
54
|
+
# code attribute reader.
|
55
|
+
attr_writer :code
|
56
|
+
|
57
|
+
# The one-byte Identifier used to match requests and responses is
|
58
|
+
# obtained as a character.
|
59
|
+
attr_reader :identifier
|
60
|
+
|
61
|
+
# The Identifer used to match RADIUS requests and responses can
|
62
|
+
# also be directly set using this.
|
63
|
+
attr_writer :identifier
|
64
|
+
|
65
|
+
# The 16-byte Authenticator field can be read as a character
|
66
|
+
# string with this attribute reader.
|
67
|
+
attr_reader :authenticator
|
68
|
+
# The authenticator field can be changed with this attribute
|
69
|
+
# writer.
|
70
|
+
attr_writer :authenticator
|
71
|
+
|
72
|
+
attr_reader :attributes
|
73
|
+
|
74
|
+
# To initialize the object, pass a Radius::Dictionary object to it.
|
75
|
+
def initialize(dict)
|
76
|
+
@dict = dict
|
77
|
+
@attributes = Hash.new(nil)
|
78
|
+
@vsattributes = Array.new
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
# I'd like to think that these methods should be built in
|
83
|
+
# the Socket class
|
84
|
+
def inet_aton(hostname)
|
85
|
+
if (hostname =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)
|
86
|
+
return((($1.to_i & 0xff) << 24) + (($2.to_i & 0xff) << 16) +
|
87
|
+
(($3.to_i & 0xff) << 8) + (($4.to_i & 0xff)))
|
88
|
+
end
|
89
|
+
return(0)
|
90
|
+
end
|
91
|
+
|
92
|
+
def inet_ntoa(iaddr)
|
93
|
+
return(sprintf("%d.%d.%d.%d", (iaddr >> 24) & 0xff, (iaddr >> 16) & 0xff,
|
94
|
+
(iaddr >> 8) & 0xff, (iaddr) & 0xff))
|
95
|
+
end
|
96
|
+
|
97
|
+
public
|
98
|
+
|
99
|
+
VSA_TYPE = 26 # type given to vendor-specific attributes
|
100
|
+
# in RFC 2138
|
101
|
+
|
102
|
+
# Given a raw RADIUS packet <tt>data</tt>, unpacks its contents so
|
103
|
+
# it can be analyzed with other methods, (e.g. +code+, +attr+,
|
104
|
+
# etc.). It also clears all present attributes.
|
105
|
+
#
|
106
|
+
# =====Parameters
|
107
|
+
# +data+:: The raw RADIUS packet to decode
|
108
|
+
def unpack(data)
|
109
|
+
p_hdr = "CCna16a*"
|
110
|
+
rcodes = {
|
111
|
+
1 => 'Access-Request',
|
112
|
+
2 => 'Access-Accept',
|
113
|
+
3 => 'Access-Reject',
|
114
|
+
4 => 'Accounting-Request',
|
115
|
+
5 => 'Accounting-Response',
|
116
|
+
11 => 'Access-Challenge',
|
117
|
+
12 => 'Status-Server',
|
118
|
+
13 => 'Status-Client'
|
119
|
+
}
|
120
|
+
|
121
|
+
@code, @identifier, len, @authenticator, attrdat = data.unpack(p_hdr)
|
122
|
+
@code = rcodes[@code]
|
123
|
+
|
124
|
+
unset_all
|
125
|
+
|
126
|
+
while attrdat.length > 0
|
127
|
+
length = attrdat.unpack("xC")[0].to_i
|
128
|
+
tval, value = attrdat.unpack("Cxa#{length-2}")
|
129
|
+
|
130
|
+
tval = tval.to_i
|
131
|
+
if tval == VSA_TYPE
|
132
|
+
# handle vendor-specific attributes
|
133
|
+
vid, vtype, vlength = value.unpack("NCC")
|
134
|
+
# XXX - How do we calculate the length of the VSA? It's not
|
135
|
+
# defined!
|
136
|
+
|
137
|
+
# XXX - 3COM seems to do things a bit differently. The 'if'
|
138
|
+
# below takes care of that. This is based on the
|
139
|
+
# Net::Radius code.
|
140
|
+
if vid == 429
|
141
|
+
# 3COM packet
|
142
|
+
vid, vtype = value.unpack("NN")
|
143
|
+
vvalue = value.unpack("xxxxxxxxa#{length - 10}")[0]
|
144
|
+
else
|
145
|
+
vvalue = value.unpack("xxxxxxa#{vlength - 2}")[0]
|
146
|
+
end
|
147
|
+
type = @dict.vsattr_numtype(vid, vtype)
|
148
|
+
if type != nil
|
149
|
+
val = case type
|
150
|
+
when 'string' then vvalue
|
151
|
+
when 'integer'
|
152
|
+
(@dict.vsaval_has_name(vid, vtype)) ?
|
153
|
+
@dict.vsaval_name(vid, vtype, vvalue.unpack("N")[0]) :
|
154
|
+
vvalue.unpack("N")[0]
|
155
|
+
when 'ipaddr' then inet_ntoa(vvalue)
|
156
|
+
when 'time' then vvalue.unpack("N")[0]
|
157
|
+
when 'date' then vvalue.unpack("N")[0]
|
158
|
+
else
|
159
|
+
raise "Unknown VSattribute type found: #{vtype}"
|
160
|
+
end
|
161
|
+
set_vsattr(vid, @dict.vsattr_name(vid, vtype), val)
|
162
|
+
end
|
163
|
+
else
|
164
|
+
type = @dict.attr_numtype(tval)
|
165
|
+
unless type.nil?
|
166
|
+
val = case type
|
167
|
+
when 'string' then value
|
168
|
+
when 'integer'
|
169
|
+
@dict.val_has_name(tval) ?
|
170
|
+
@dict.val_name(tval, value.unpack("N")[0]) :
|
171
|
+
value.unpack("N")[0]
|
172
|
+
when 'ipaddr' then inet_ntoa(value.unpack("N")[0])
|
173
|
+
when 'time' then value.unpack("N")[0]
|
174
|
+
when 'date' then value.unpack("N")[0]
|
175
|
+
when 'octets' then value
|
176
|
+
else raise "Unknown attribute type found: #{type}"
|
177
|
+
end
|
178
|
+
set_attr(@dict.attr_name(tval), val)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
attrdat[0, length] = ""
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# The Radius::Packet object contains attributes that can be set
|
186
|
+
# and altered with the object's accessor methods, or obtained from
|
187
|
+
# the unpack method. This method will return a raw RADIUS
|
188
|
+
# packet that should be suitable for sending to a RADIUS client or
|
189
|
+
# server over UDP as per RFC 2138.
|
190
|
+
#
|
191
|
+
# =====Return Value
|
192
|
+
# The RADIUS packet corresponding to the object's current internal
|
193
|
+
# state.
|
194
|
+
def pack
|
195
|
+
hdrlen = 1 + 1 + 2 + 16 # size of packet header
|
196
|
+
p_hdr = "CCna16a*" # pack template for header
|
197
|
+
p_attr = "CCa*" # pack template for attribute
|
198
|
+
p_vsa = "CCNCCa*" # pack template for VSA's
|
199
|
+
p_vsa_3com = "CCNNa*" # used by 3COM devices
|
200
|
+
|
201
|
+
codes = {
|
202
|
+
'Access-Request' => 1,
|
203
|
+
'Access-Accept' => 2,
|
204
|
+
'Access-Reject' => 3,
|
205
|
+
'Accounting-Request' => 4,
|
206
|
+
'Accounting-Response' => 5,
|
207
|
+
'Access-Challenge' => 11,
|
208
|
+
'Status-Server' => 12,
|
209
|
+
'Status-Client' => 13
|
210
|
+
}
|
211
|
+
attstr = ""
|
212
|
+
each do |attr, value|
|
213
|
+
anum = @dict.attr_num(attr)
|
214
|
+
val = case @dict.attr_type(attr)
|
215
|
+
when "string" then value
|
216
|
+
when "integer"
|
217
|
+
[@dict.attr_has_val(anum) ?
|
218
|
+
@dict.val_num(anum, value) ? @dict.val_num(anum, value) : value : value].pack("N")
|
219
|
+
when "ipaddr" then [inet_aton(value)].pack("N")
|
220
|
+
when "date" then [value].pack("N")
|
221
|
+
when "time" then [value].pack("N")
|
222
|
+
when "octets" then value
|
223
|
+
else
|
224
|
+
next
|
225
|
+
end
|
226
|
+
attstr += [@dict.attr_num(attr), val.length + 2, val].pack(p_attr)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Pack vendor-specific attributes
|
230
|
+
each_vsa do |vendor, attr, datum|
|
231
|
+
code = @dict.vsattr_num(vendor, attr)
|
232
|
+
vval = case @dict.vsattr_type(vendor, attr)
|
233
|
+
when "string" then datum
|
234
|
+
when "integer"
|
235
|
+
@dict.vsattr_has_val(vendor.to_i, code) ?
|
236
|
+
[@dict.vsaval_num(vendor, code, datum)].pack("N") :
|
237
|
+
[datum].pack("N")
|
238
|
+
when "ipaddr" then inet_aton(datum)
|
239
|
+
when "time" then [datum].pack("N")
|
240
|
+
when "date" then [datum].pack("N")
|
241
|
+
when "octets" then value
|
242
|
+
else
|
243
|
+
next
|
244
|
+
end
|
245
|
+
if vendor == 429
|
246
|
+
# For 3COM devices
|
247
|
+
attstr += [VSA_TYPE, vval.length + 10, vendor,
|
248
|
+
@dict.vsattr_num(vendor, attr), vval].pack(p_vsa_3com)
|
249
|
+
else
|
250
|
+
attstr += [VSA_TYPE, vval.length + 8, vendor,
|
251
|
+
@dict.vsattr_num(vendor, attr), vval.length + 2,
|
252
|
+
vval].pack(p_vsa)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
return([codes[@code], @identifier, attstr.length + hdrlen,
|
256
|
+
@authenticator, attstr].pack(p_hdr))
|
257
|
+
end
|
258
|
+
|
259
|
+
# This method is provided a block which will pass every
|
260
|
+
# attribute-value pair currently available.
|
261
|
+
def each
|
262
|
+
@attributes.each_pair do |key, value|
|
263
|
+
yield(key, value)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# The value of the named attribute in the object's internal state
|
268
|
+
# can be obtained.
|
269
|
+
#
|
270
|
+
# ====Parameters
|
271
|
+
# +name+:: the name of the attribute to obtain
|
272
|
+
#
|
273
|
+
# ====Return value:
|
274
|
+
# The value of the attribute is returned.
|
275
|
+
def attr(name)
|
276
|
+
return(@attributes[name])
|
277
|
+
end
|
278
|
+
|
279
|
+
# Changes the value of the named attribute.
|
280
|
+
#
|
281
|
+
# ====Parameters
|
282
|
+
# +name+:: The name of the attribute to set
|
283
|
+
# +value+:: The value of the attribute
|
284
|
+
def set_attr(name, value)
|
285
|
+
@attributes[name] = value
|
286
|
+
end
|
287
|
+
|
288
|
+
# Undefines the current value of the named attribute.
|
289
|
+
#
|
290
|
+
# ====Parameters
|
291
|
+
# +name+:: The name of the attribute to unset
|
292
|
+
def unset_attr(name)
|
293
|
+
@attributes[name] = nil
|
294
|
+
end
|
295
|
+
|
296
|
+
# Undefines all attributes.
|
297
|
+
#
|
298
|
+
def unset_all_attr
|
299
|
+
each do |key, value|
|
300
|
+
unset_attr(key)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# This method will pass each vendor-specific attribute available
|
305
|
+
# to a passed block. The parameters to the block are the vendor
|
306
|
+
# ID, the attribute name, and the attribute value.
|
307
|
+
def each_vsa
|
308
|
+
@vsattributes.each_index do |vendorid|
|
309
|
+
if @vsattributes[vendorid] != nil
|
310
|
+
@vsattributes[vendorid].each_pair do |key, value|
|
311
|
+
value.each do |val|
|
312
|
+
yield(vendorid, key, val)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# This method is an iterator that passes each vendor-specific
|
320
|
+
# attribute associated with a vendor ID.
|
321
|
+
def each_vsaval(vendorid)
|
322
|
+
@vsattributes[vendorid].each_pair do |key, value|
|
323
|
+
yield(key, value)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Changes the value of the named vendor-specific attribute.
|
328
|
+
#
|
329
|
+
# ====Parameters
|
330
|
+
# +vendorid+:: The vendor ID for the VSA to set
|
331
|
+
# +name+:: The name of the attribute to set
|
332
|
+
# +value+:: The value of the attribute
|
333
|
+
def set_vsattr(vendorid, name, value)
|
334
|
+
if @vsattributes[vendorid] == nil
|
335
|
+
@vsattributes[vendorid] = Hash.new(nil)
|
336
|
+
end
|
337
|
+
if @vsattributes[vendorid][name] == nil
|
338
|
+
@vsattributes[vendorid][name] = Array.new
|
339
|
+
end
|
340
|
+
@vsattributes[vendorid][name].push(value)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Undefines the current value of the named vendor-specific
|
344
|
+
# attribute.
|
345
|
+
#
|
346
|
+
# ====Parameters
|
347
|
+
# +vendorid+:: The vendor ID for the VSA to set
|
348
|
+
# +name+:: The name of the attribute to unset
|
349
|
+
def unset_vsattr(vendorid, name)
|
350
|
+
return if @vsattributes[vendorid] == nil
|
351
|
+
@vsattributes[vendorid][name] = nil
|
352
|
+
end
|
353
|
+
|
354
|
+
# Undefines all vendor-specific attributes.
|
355
|
+
#
|
356
|
+
def unset_all_vsattr
|
357
|
+
each_vsa do |vendor, attr, datum|
|
358
|
+
unset_vsattr(vendor, attr)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Undefines all regular and vendor-specific attributes
|
363
|
+
def unset_all
|
364
|
+
unset_all_attr
|
365
|
+
unset_all_vsattr
|
366
|
+
end
|
367
|
+
|
368
|
+
# This method obtains the value of a vendor-specific attribute,
|
369
|
+
# given the vendor ID and the name of the vendor-specific
|
370
|
+
# attribute.
|
371
|
+
# ====Parameters
|
372
|
+
# +vendorid+:: the vendor ID
|
373
|
+
# +name+:: the name of the attribute to obtain
|
374
|
+
#
|
375
|
+
# ====Return value:
|
376
|
+
# The value of the vendor-specific attribute is returned.
|
377
|
+
def vsattr(vendorid, name)
|
378
|
+
return(nil) if @vsattributes[vendorid] == nil
|
379
|
+
return(@vsattributes[vendorid][name])
|
380
|
+
end
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
# Exclusive-or character by character two strings.
|
385
|
+
# returns a new string that is the xor of str1 and str2. The
|
386
|
+
# two strings must be the same length.
|
387
|
+
def xor_str(str1, str2)
|
388
|
+
raise RuntimeError unless str1.size == str2.size
|
389
|
+
|
390
|
+
memo = ''.encode('US-ASCII')
|
391
|
+
str1.bytes.zip(str2.bytes).map {|c1, c2| c1 ^ c2 }.reduce(memo) {|str, i| str << i }
|
392
|
+
end
|
393
|
+
|
394
|
+
public
|
395
|
+
|
396
|
+
# The RADIUS User-Password attribute is encoded with a shared
|
397
|
+
# secret. This method will return the decoded version given the
|
398
|
+
# shared secret. This also works when the attribute name is
|
399
|
+
# 'Password' for compatibility reasons.
|
400
|
+
# ====Parameters
|
401
|
+
# +secret+:: The shared secret of the RADIUS system
|
402
|
+
# ====Return
|
403
|
+
# The cleartext version of the User-Password.
|
404
|
+
def password(secret)
|
405
|
+
pwdin = attr("User-Password") || attr("Password")
|
406
|
+
pwdout = ""
|
407
|
+
lastround = @authenticator
|
408
|
+
|
409
|
+
0.step(pwdin.length-1, 16) do |i|
|
410
|
+
pwdout = xor_str(pwdin[i, 16],
|
411
|
+
Digest::MD5.digest(secret + lastround))
|
412
|
+
lastround = pwdin[i, 16]
|
413
|
+
end
|
414
|
+
|
415
|
+
pwdout.sub(/\000+$/, "") if pwdout
|
416
|
+
pwdout[length.pwdin, -1] = "" unless (pwdout.length <= pwdin.length)
|
417
|
+
return(pwdout)
|
418
|
+
end
|
419
|
+
|
420
|
+
def check_password(given, secret)
|
421
|
+
given += "\000" * (15 - (15 + given.length) % 16)
|
422
|
+
|
423
|
+
pwdout = "".force_encoding("ASCII-8BIT")
|
424
|
+
lastround = @authenticator
|
425
|
+
|
426
|
+
0.step(given.length() -1, 16) do |i|
|
427
|
+
lastround = xor_str(given[i, 16], Digest::MD5.digest(secret + lastround))
|
428
|
+
pwdout += lastround.force_encoding("ASCII-8BIT")
|
429
|
+
end
|
430
|
+
|
431
|
+
pwdout.sub(/\000+$/, "") if pwdout
|
432
|
+
actual_password = @attributes["User-Password"].force_encoding("ASCII-8BIT")
|
433
|
+
|
434
|
+
pwdout == actual_password
|
435
|
+
end
|
436
|
+
|
437
|
+
# The RADIUS User-Password attribute is encoded with a shared
|
438
|
+
# secret. This method will prepare the encoded version of the
|
439
|
+
# password. Note that this method <em>always</em> stores the
|
440
|
+
# encrypted password in the 'User-Password' attribute. Some
|
441
|
+
# (non-RFC 2138-compliant) servers have been reported that insist
|
442
|
+
# on using the 'Password' attribute instead.
|
443
|
+
#
|
444
|
+
# ====Parameters
|
445
|
+
# +pwdin++:: The password to encrypt
|
446
|
+
# +secret+:: The shared secret of the RADIUS system
|
447
|
+
#
|
448
|
+
def set_password(pwdin, secret)
|
449
|
+
lastround = @authenticator
|
450
|
+
pwdout = ""
|
451
|
+
|
452
|
+
# pad to 16n bytes
|
453
|
+
pwdin += "\000" * (15 - (15 + pwdin.length) % 16)
|
454
|
+
|
455
|
+
0.step(pwdin.length-1, 16) do |i|
|
456
|
+
lastround = xor_str(pwdin[i, 16], Digest::MD5.digest(secret + lastround))
|
457
|
+
pwdout += lastround
|
458
|
+
end
|
459
|
+
|
460
|
+
set_attr("User-Password", pwdout)
|
461
|
+
return(pwdout)
|
462
|
+
end
|
463
|
+
|
464
|
+
# This method will convert a RADIUS packet into a printable
|
465
|
+
# string. Any fields in the packet that might possibly contain
|
466
|
+
# non-printable characters are turned into Base64 strings.
|
467
|
+
#
|
468
|
+
# ====Parameters
|
469
|
+
# +secret+:: The shared secret of the RADIUS system. Pass nil if
|
470
|
+
# you don't want to see <tt>User-Password</tt> attributes decoded.
|
471
|
+
#
|
472
|
+
# ====Return
|
473
|
+
# The string representation of the RADIUS packet.
|
474
|
+
#
|
475
|
+
def to_s
|
476
|
+
str = "RAD-Code = #{@code}\n"
|
477
|
+
str += "RAD-Identifier = #{@identifier}\n"
|
478
|
+
str += "RAD-Authenticator = #{[@authenticator].pack('m')}"
|
479
|
+
each do |attr, val|
|
480
|
+
str += "#{attr} = #{val}\n"
|
481
|
+
end
|
482
|
+
|
483
|
+
each_vsa do |vendorid, vsaname, val|
|
484
|
+
str += "Vendor-Id: #{vendorid} -- #{vsaname} = #{val}\n"
|
485
|
+
end
|
486
|
+
return(str)
|
487
|
+
end
|
488
|
+
|
489
|
+
# Given a (packed) RADIUS packet and a shared secret, returns a
|
490
|
+
# new packet with the authenticator field changed in accordance
|
491
|
+
# with RADIUS protocol requirements.
|
492
|
+
#
|
493
|
+
# ====Parameters
|
494
|
+
# +packed_packet+:: The packed packet to compute a new Authenticator field for.
|
495
|
+
# +secret+:: The shared secret of the RADIUS system.
|
496
|
+
# ====Return value
|
497
|
+
# a new packed packet with the authenticator field recomputed.
|
498
|
+
def Packet.auth_resp(packed_packet, secret)
|
499
|
+
new = String.new(packed_packet)
|
500
|
+
new[4, 16] = Digest::MD5.digest(packed_packet + secret)
|
501
|
+
return(new)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|