Class: Radius::Packet

Inherits:
Object
  • Object
show all
Defined in:
lib/radius/packet.rb

Constant Summary collapse

VSA_TYPE =

type given to vendor-specific attributes

26

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dict) ⇒ Packet

To initialize the object, pass a Radius::Dictionary object to it.



75
76
77
78
79
# File 'lib/radius/packet.rb', line 75

def initialize(dict)
  @dict = dict
  @attributes = Hash.new(nil)
  @vsattributes = Array.new
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



72
73
74
# File 'lib/radius/packet.rb', line 72

def attributes
  @attributes
end

#authenticatorObject

The 16-byte Authenticator field can be read as a character string with this attribute reader.



67
68
69
# File 'lib/radius/packet.rb', line 67

def authenticator
  @authenticator
end

#codeObject

The code field is returned as a string. As of this writing, the following codes are recognized:

Access-Request          Access-Accept
Access-Reject           Accounting-Request
Accounting-Response     Access-Challenge
Status-Server           Status-Client


51
52
53
# File 'lib/radius/packet.rb', line 51

def code
  @code
end

#identifierObject

The one-byte Identifier used to match requests and responses is obtained as a character.



59
60
61
# File 'lib/radius/packet.rb', line 59

def identifier
  @identifier
end

Class Method Details

.auth_resp(packed_packet, secret) ⇒ Object

Given a (packed) RADIUS packet and a shared secret, returns a new packet with the authenticator field changed in accordance with RADIUS protocol requirements.

Parameters

packed_packet

The packed packet to compute a new Authenticator field for.

secret

The shared secret of the RADIUS system.

Return value

a new packed packet with the authenticator field recomputed.



498
499
500
501
502
# File 'lib/radius/packet.rb', line 498

def Packet.auth_resp(packed_packet, secret)
  new = String.new(packed_packet)
  new[4, 16] = Digest::MD5.digest(packed_packet + secret)
  return(new)
end

Instance Method Details

#attr(name) ⇒ Object

The value of the named attribute in the object’s internal state can be obtained.

Parameters

name

the name of the attribute to obtain

Return value:

The value of the attribute is returned.



275
276
277
# File 'lib/radius/packet.rb', line 275

def attr(name)
  return(@attributes[name])
end

#check_password(given, secret) ⇒ Object



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/radius/packet.rb', line 420

def check_password(given, secret)
  given += "\000" * (15 - (15 + given.length) % 16)

  pwdout = "".force_encoding("ASCII-8BIT")
  lastround = @authenticator

  0.step(given.length() -1, 16) do |i|
    lastround = xor_str(given[i, 16], Digest::MD5.digest(secret + lastround))
    pwdout += lastround.force_encoding("ASCII-8BIT")
  end

  pwdout.sub(/\000+$/, "") if pwdout
  actual_password = @attributes["User-Password"].force_encoding("ASCII-8BIT")

  pwdout == actual_password
end

#eachObject

This method is provided a block which will pass every attribute-value pair currently available.



261
262
263
264
265
# File 'lib/radius/packet.rb', line 261

def each
  @attributes.each_pair do |key, value|
    yield(key, value)
  end
end

#each_vsaObject

This method will pass each vendor-specific attribute available to a passed block. The parameters to the block are the vendor ID, the attribute name, and the attribute value.



307
308
309
310
311
312
313
314
315
316
317
# File 'lib/radius/packet.rb', line 307

def each_vsa
  @vsattributes.each_index do |vendorid|
    if @vsattributes[vendorid] != nil
      @vsattributes[vendorid].each_pair do |key, value|
        value.each do |val|
          yield(vendorid, key, val)
        end
      end
    end
  end
end

#each_vsaval(vendorid) ⇒ Object

This method is an iterator that passes each vendor-specific attribute associated with a vendor ID.



321
322
323
324
325
# File 'lib/radius/packet.rb', line 321

def each_vsaval(vendorid)
  @vsattributes[vendorid].each_pair do |key, value|
    yield(key, value)
  end
end

#packObject

The Radius::Packet object contains attributes that can be set and altered with the object’s accessor methods, or obtained from the unpack method. This method will return a raw RADIUS packet that should be suitable for sending to a RADIUS client or server over UDP as per RFC 2138.

Return Value

The RADIUS packet corresponding to the object’s current internal state.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/radius/packet.rb', line 194

def pack
  hdrlen = 1 + 1 + 2 + 16	# size of packet header
  p_hdr = "CCna16a*"	# pack template for header
  p_attr = "CCa*"		# pack template for attribute
  p_vsa = "CCNCCa*"		# pack template for VSA's
  p_vsa_3com = "CCNNa*"	# used by 3COM devices

  codes = {
    'Access-Request' => 1,
    'Access-Accept' => 2,
    'Access-Reject' => 3,
    'Accounting-Request' => 4,
    'Accounting-Response' => 5,
    'Access-Challenge' => 11,
    'Status-Server' => 12,
    'Status-Client' => 13
  }
  attstr = ""
  each do |attr, value|
    anum = @dict.attr_num(attr)
    val = case @dict.attr_type(attr)
          when "string" then value
          when "integer"
            [@dict.attr_has_val(anum) ?
        @dict.val_num(anum, value) ? @dict.val_num(anum, value) : value : value].pack("N")
          when "ipaddr" then [inet_aton(value)].pack("N")
          when "date" then [value].pack("N")
          when "time" then [value].pack("N")
          when "octets" then value
          else
            next
          end
    attstr += [@dict.attr_num(attr), val.length + 2, val].pack(p_attr)
  end

  # Pack vendor-specific attributes
  each_vsa do |vendor, attr, datum|
    code = @dict.vsattr_num(vendor, attr)
    vval = case @dict.vsattr_type(vendor, attr)
           when "string" then datum
           when "integer"
             @dict.vsattr_has_val(vendor.to_i, code) ?
             [@dict.vsaval_num(vendor, code, datum)].pack("N") :
               [datum].pack("N")
           when "ipaddr" then inet_aton(datum)
           when "time" then [datum].pack("N")
           when "date" then [datum].pack("N")
           when "octets" then value
           else
             next
           end
    if vendor == 429
      # For 3COM devices
      attstr += [VSA_TYPE, vval.length + 10, vendor,
        @dict.vsattr_num(vendor, attr), vval].pack(p_vsa_3com)
    else
      attstr += [VSA_TYPE, vval.length + 8, vendor,
        @dict.vsattr_num(vendor, attr), vval.length + 2,
        vval].pack(p_vsa)
    end
  end
  return([codes[@code], @identifier, attstr.length + hdrlen,
      @authenticator, attstr].pack(p_hdr))
end

#password(secret) ⇒ Object

The RADIUS User-Password attribute is encoded with a shared secret. This method will return the decoded version given the shared secret. This also works when the attribute name is ‘Password’ for compatibility reasons.

Parameters

secret

The shared secret of the RADIUS system

Return

The cleartext version of the User-Password.



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/radius/packet.rb', line 404

def password(secret)
  pwdin = attr("User-Password") || attr("Password")
  pwdout = ""
  lastround = @authenticator

  0.step(pwdin.length-1, 16) do |i|
    pwdout = xor_str(pwdin[i, 16],
      Digest::MD5.digest(secret + lastround))
    lastround = pwdin[i, 16]
  end

  pwdout.sub(/\000+$/, "") if pwdout
  pwdout[length.pwdin, -1] = "" unless (pwdout.length <= pwdin.length)
  return(pwdout)
end

#set_attr(name, value) ⇒ Object

Changes the value of the named attribute.

Parameters

name

The name of the attribute to set

value

The value of the attribute



284
285
286
# File 'lib/radius/packet.rb', line 284

def set_attr(name, value)
  @attributes[name] = value
end

#set_password(pwdin, secret) ⇒ Object

The RADIUS User-Password attribute is encoded with a shared secret. This method will prepare the encoded version of the password. Note that this method always stores the encrypted password in the ‘User-Password’ attribute. Some (non-RFC 2138-compliant) servers have been reported that insist on using the ‘Password’ attribute instead.

Parameters

pwdin+

The password to encrypt

secret

The shared secret of the RADIUS system



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/radius/packet.rb', line 448

def set_password(pwdin, secret)
  lastround = @authenticator
  pwdout = ""

  # pad to 16n bytes
  pwdin += "\000" * (15 - (15 + pwdin.length) % 16)

  0.step(pwdin.length-1, 16) do |i|
    lastround = xor_str(pwdin[i, 16], Digest::MD5.digest(secret + lastround))
    pwdout += lastround
  end

  set_attr("User-Password", pwdout)
  return(pwdout)
end

#set_vsattr(vendorid, name, value) ⇒ Object

Changes the value of the named vendor-specific attribute.

Parameters

vendorid

The vendor ID for the VSA to set

name

The name of the attribute to set

value

The value of the attribute



333
334
335
336
337
338
339
340
341
# File 'lib/radius/packet.rb', line 333

def set_vsattr(vendorid, name, value)
  if @vsattributes[vendorid] == nil
    @vsattributes[vendorid] = Hash.new(nil)
  end
  if @vsattributes[vendorid][name] == nil
    @vsattributes[vendorid][name] = Array.new
  end
  @vsattributes[vendorid][name].push(value)
end

#to_sObject

This method will convert a RADIUS packet into a printable string. Any fields in the packet that might possibly contain non-printable characters are turned into Base64 strings.

Parameters

secret

The shared secret of the RADIUS system. Pass nil if

you don’t want to see User-Password attributes decoded.

Return

The string representation of the RADIUS packet.



475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/radius/packet.rb', line 475

def to_s
  str = "RAD-Code = #{@code}\n"
  str += "RAD-Identifier = #{@identifier}\n"
  str += "RAD-Authenticator = #{[@authenticator].pack('m')}"
  each do |attr, val|
    str += "#{attr} = #{val}\n"
  end

  each_vsa do |vendorid, vsaname, val|
    str += "Vendor-Id: #{vendorid} -- #{vsaname} = #{val}\n"
  end
  return(str)
end

#unpack(data) ⇒ Object

Given a raw RADIUS packet data, unpacks its contents so it can be analyzed with other methods, (e.g. code, attr, etc.). It also clears all present attributes.

Parameters
data

The raw RADIUS packet to decode



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/radius/packet.rb', line 108

def unpack(data)
  p_hdr = "CCna16a*"
  rcodes = {
    1 => 'Access-Request',
    2  => 'Access-Accept',
    3  => 'Access-Reject',
    4  => 'Accounting-Request',
    5  => 'Accounting-Response',
    11 => 'Access-Challenge',
    12 => 'Status-Server',
    13 => 'Status-Client'
  }

  @code, @identifier, len, @authenticator, attrdat = data.unpack(p_hdr)
  @code = rcodes[@code]

  unset_all

  while attrdat.length > 0
    length = attrdat.unpack("xC")[0].to_i
    tval, value = attrdat.unpack("Cxa#{length-2}")

    tval = tval.to_i
    if tval == VSA_TYPE
      # handle vendor-specific attributes
      vid, vtype, vlength = value.unpack("NCC")
      # XXX - How do we calculate the length of the VSA?  It's not
      # defined!

      # XXX - 3COM seems to do things a bit differently.  The 'if'
      # below takes care of that.  This is based on the
      # Net::Radius code.
      if vid == 429
        # 3COM packet
        vid, vtype = value.unpack("NN")
        vvalue = value.unpack("xxxxxxxxa#{length - 10}")[0]
      else
        vvalue = value.unpack("xxxxxxa#{vlength - 2}")[0]
      end
      type = @dict.vsattr_numtype(vid, vtype)
      if type != nil
        val = case type
              when 'string' then vvalue
              when 'integer'
                (@dict.vsaval_has_name(vid, vtype)) ?
                @dict.vsaval_name(vid, vtype, vvalue.unpack("N")[0]) :
                  vvalue.unpack("N")[0]
              when 'ipaddr' then inet_ntoa(vvalue)
              when 'time' then vvalue.unpack("N")[0]
              when 'date' then vvalue.unpack("N")[0]
              else
                raise "Unknown VSattribute type found: #{vtype}"
              end
        set_vsattr(vid, @dict.vsattr_name(vid, vtype), val)
      end
    else
      type = @dict.attr_numtype(tval)
      unless type.nil?
        val = case type
              when 'string' then value
              when 'integer'
                @dict.val_has_name(tval) ?
                @dict.val_name(tval, value.unpack("N")[0]) :
                  value.unpack("N")[0]
              when 'ipaddr' then inet_ntoa(value.unpack("N")[0])
              when 'time' then value.unpack("N")[0]
              when 'date' then value.unpack("N")[0]
              when 'octets' then value
              else raise "Unknown attribute type found: #{type}"
              end
        set_attr(@dict.attr_name(tval), val)
      end
    end
    attrdat[0, length] = ""
  end
end

#unset_allObject

Undefines all regular and vendor-specific attributes



363
364
365
366
# File 'lib/radius/packet.rb', line 363

def unset_all
  unset_all_attr
  unset_all_vsattr
end

#unset_all_attrObject

Undefines all attributes.



298
299
300
301
302
# File 'lib/radius/packet.rb', line 298

def unset_all_attr
  each do |key, value|
    unset_attr(key)
  end
end

#unset_all_vsattrObject

Undefines all vendor-specific attributes.



356
357
358
359
360
# File 'lib/radius/packet.rb', line 356

def unset_all_vsattr
  each_vsa do |vendor, attr, datum|
    unset_vsattr(vendor, attr)
  end
end

#unset_attr(name) ⇒ Object

Undefines the current value of the named attribute.

Parameters

name

The name of the attribute to unset



292
293
294
# File 'lib/radius/packet.rb', line 292

def unset_attr(name)
  @attributes[name] = nil
end

#unset_vsattr(vendorid, name) ⇒ Object

Undefines the current value of the named vendor-specific attribute.

Parameters

vendorid

The vendor ID for the VSA to set

name

The name of the attribute to unset



349
350
351
352
# File 'lib/radius/packet.rb', line 349

def unset_vsattr(vendorid, name)
  return if @vsattributes[vendorid] == nil
  @vsattributes[vendorid][name] = nil
end

#vsattr(vendorid, name) ⇒ Object

This method obtains the value of a vendor-specific attribute, given the vendor ID and the name of the vendor-specific attribute.

Parameters

vendorid

the vendor ID

name

the name of the attribute to obtain

Return value:

The value of the vendor-specific attribute is returned.



377
378
379
380
# File 'lib/radius/packet.rb', line 377

def vsattr(vendorid, name)
  return(nil) if @vsattributes[vendorid] == nil
  return(@vsattributes[vendorid][name])
end