Created
September 14, 2013 21:35
-
-
Save meitinger/6565862 to your computer and use it in GitHub Desktop.
Implementation of an easy-to-expand, type-safe RADIUS packet class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Copyright (C) 2012-2013, Manuel Meitinger | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 2 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Net; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace Aufbauwerk.Net.Radius | |
{ | |
#region Enumerations | |
public enum PacketCode : byte | |
{ | |
AccessRequest = 1, | |
AccessAccept = 2, | |
AccessReject = 3, | |
AccountingRequest = 4, | |
AccountingResponse = 5, | |
AccessChallenge = 11, | |
} | |
public enum ServiceType | |
{ | |
Login = 1, | |
Framed = 2, | |
CallbackLogin = 3, | |
CallbackFramed = 4, | |
Outbound = 5, | |
Administrative = 6, | |
NasPrompt = 7, | |
AuthenticateOnly = 8, | |
CallbackNasPrompt = 9, | |
CallCheck = 10, | |
CallbackAdministrative = 11, | |
} | |
public enum FramedProtocol | |
{ | |
Ppp = 1, | |
Slip = 2, | |
Arap = 3, | |
Gandalf = 4, | |
Xylogics = 5, | |
X75Synchronous = 6, | |
} | |
public enum FramedRouting | |
{ | |
None = 0, | |
Send = 1, | |
Listen = 2, | |
SendAndListen = 3, | |
} | |
public enum FramedCompression | |
{ | |
None = 0, | |
TcpIp = 1, | |
Ipx = 2, | |
Lzs = 3, | |
} | |
public enum LoginService | |
{ | |
Telnet = 0, | |
Rlogin = 1, | |
TcpClear = 2, | |
PortMaster = 3, | |
Lat = 4, | |
X25Pad = 5, | |
X25T3Pos = 6, | |
TcpClearQuiet = 8, | |
} | |
public enum TerminationAction | |
{ | |
Default = 0, | |
RadiusRequest = 1, | |
} | |
public enum AccountingStatusType | |
{ | |
Start = 1, | |
Stop = 2, | |
InterimUpdate = 3, | |
Alive = 3, | |
AccountingOn = 7, | |
AccountingOff = 8, | |
Failed = 15, | |
} | |
public enum AccountingAuthentic | |
{ | |
Radius = 1, | |
Local = 2, | |
Remote = 3, | |
} | |
public enum AccountingTerminateCause | |
{ | |
UserRequest = 1, | |
LostCarrier = 2, | |
LostService = 3, | |
IdleTimeout = 4, | |
SessionTimeout = 5, | |
AdminReset = 6, | |
AdminReboot = 7, | |
PortError = 8, | |
NasError = 9, | |
NasRequest = 10, | |
NasReboot = 11, | |
PortUnneeded = 12, | |
PortPreempted = 13, | |
PortSuspended = 14, | |
ServiceUnavailable = 15, | |
Callback = 16, | |
UserError = 17, | |
HostRequest = 18, | |
} | |
public enum NasPortType | |
{ | |
Async = 0, | |
Sync = 1, | |
IsdnSync = 2, | |
IsdnAsyncV120 = 3, | |
IsdnAsyncV110 = 4, | |
Virtual = 5, | |
Piafs = 6, | |
HdlcClearChannel = 7, | |
X25 = 8, | |
X75 = 9, | |
G3Fax = 10, | |
Sdsl = 11, | |
AdslCap = 12, | |
AdslDmt = 13, | |
Idsl = 14, | |
Ethernet = 15, | |
Xdsl = 16, | |
Cable = 17, | |
WirelessOther = 18, | |
WirelessIeee802Dot11 = 19, | |
} | |
public enum Prompt | |
{ | |
NoEcho = 0, | |
Echo = 1, | |
} | |
#endregion | |
/// <summary> | |
/// A RADIUS packet implementation. | |
/// </summary> | |
public class RadiusPacket | |
{ | |
private static readonly RandomNumberGenerator RNG = RandomNumberGenerator.Create(); | |
private static readonly byte[] EmptyAuthenticator = new byte[16]; | |
private readonly Dictionary<byte, List<int>> attributeOffsets = new Dictionary<byte, List<int>>(); | |
private readonly byte[] buffer; | |
/// <summary> | |
/// Creates an empty RADIUS packet. | |
/// </summary> | |
/// <param name="code">The packet type.</param> | |
public RadiusPacket(PacketCode code) | |
{ | |
this.buffer = new byte[4096]; | |
IsReadOnly = false; | |
Code = code; | |
Length = 20; | |
} | |
/// <summary> | |
/// Parses a given binary representation of a RADIUS packet. | |
/// </summary> | |
/// <param name="buffer">The byte array to parse.</param> | |
/// <param name="length">The amount of bytes to parse.</param> | |
/// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <c>null</c>.</exception> | |
/// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is negative or greater then the length of <paramref name="buffer"/>.</exception> | |
/// <exception cref="FormatException"><paramref name="buffer"/> contains invalid data.</exception> | |
public RadiusPacket(byte[] buffer, int length) | |
{ | |
if (buffer == null) throw new ArgumentNullException("buffer"); | |
if (length < 0 || length > buffer.Length) throw new ArgumentOutOfRangeException("length"); | |
this.buffer = buffer; | |
IsReadOnly = false; | |
if (length < 20 || Length < 20) | |
throw new FormatException("Message is too short"); | |
if (Length > length) | |
throw new FormatException("Message is incomplete"); | |
if (length > 4096) | |
throw new FormatException("Message is too long"); | |
var offset = 20; | |
while (offset < Length) | |
{ | |
if (offset + 2 > Length) | |
throw new FormatException("Attribute is incomplete"); | |
var newOffset = offset + buffer[offset + 1]; | |
if (newOffset > Length) | |
throw new FormatException(string.Format("Attribute {0} overruns buffer", buffer[offset])); | |
List<int> offsets; | |
if (!attributeOffsets.TryGetValue(buffer[offset], out offsets)) | |
attributeOffsets.Add(buffer[offset], offsets = new List<int>()); | |
offsets.Add(offset); | |
offset = newOffset; | |
} | |
IsReadOnly = true; | |
} | |
private void CorrectOffsets(int offset, int length) | |
{ | |
// adjust the packet size, move the actual data and update all affected offsets | |
Length += length; | |
Array.Copy(buffer, offset, buffer, offset + length, Length - (offset + length)); | |
foreach (var offsets in attributeOffsets.Values) | |
for (var i = offsets.Count - 1; i >= 0; i--) | |
if (offsets[i] >= offset) | |
offsets[i] += length; | |
} | |
private List<int> OffsetsFromParser<T>(RadiusAttributeParser<T> parser) | |
{ | |
// return the offsets for a given attribute type | |
List<int> offsets; | |
if (!attributeOffsets.TryGetValue(parser.Type, out offsets)) | |
attributeOffsets.Add(parser.Type, offsets = new List<int>()); | |
return offsets; | |
} | |
private bool Verify(byte[] authenticatorBuffer, int offset, byte[] sharedSecret) | |
{ | |
// create the md5 of code+identifier+original_authenticator+attributes+shared_secret and compare it with the authenticator | |
using (var md5 = MD5.Create()) | |
{ | |
md5.TransformBlock(buffer, 0, 4, buffer, 0); | |
md5.TransformBlock(authenticatorBuffer, offset, 16, authenticatorBuffer, 0); | |
md5.TransformBlock(buffer, 20, Length - 20, buffer, 20); | |
md5.TransformFinalBlock(sharedSecret, 0, sharedSecret.Length); | |
var hash = md5.Hash; | |
for (var i = 0; i < 16; i++) | |
if (hash[i] != buffer[i + 4]) | |
return false; | |
return true; | |
} | |
} | |
private void EnsureWritable() | |
{ | |
// throw an exception if the packet is read-only | |
if (IsReadOnly) | |
throw new NotSupportedException("Packet is read-only"); | |
} | |
private void Sign(byte[] authenticatorBuffer, int offset, byte[] sharedSecret) | |
{ | |
// check for not read-only | |
EnsureWritable(); | |
// set the given authenticator, create the md5 of the packet and store it as the new authenticator | |
Array.Copy(authenticatorBuffer, offset, buffer, 4, 16); | |
using (var md5 = MD5.Create()) | |
{ | |
md5.TransformBlock(buffer, 0, Length, buffer, 0); | |
md5.TransformFinalBlock(sharedSecret, 0, sharedSecret.Length); | |
Array.Copy(md5.Hash, 0, buffer, 4, 16); | |
} | |
} | |
internal void InsertAttribute<T>(RadiusAttributeParser<T> parser, int index, T item) | |
{ | |
// check for not read-only | |
EnsureWritable(); | |
// determine offset | |
var offsets = OffsetsFromParser(parser); | |
var offset = index == offsets.Count ? Length : offsets[index]; | |
// parse value into a byte array | |
var attribute = parser.Write(item); | |
var length = attribute.Length + 2; | |
if (length > byte.MaxValue) | |
throw new ArgumentOutOfRangeException("item"); | |
// prepare buffer | |
CorrectOffsets(offset, length); | |
// write attribute | |
buffer[offset] = parser.Type; | |
buffer[offset + 1] = (byte)length; | |
Array.Copy(attribute, 0, buffer, offset + 2, length - 2); | |
// store offset | |
offsets.Insert(index, offset); | |
} | |
internal void RemoveAttribute<T>(RadiusAttributeParser<T> parser, int index) | |
{ | |
// check for not read-only | |
EnsureWritable(); | |
// determine offset and length | |
var offsets = OffsetsFromParser(parser); | |
var offset = offsets[index]; | |
var length = buffer[offset + 1]; | |
// remove attribute from buffer | |
CorrectOffsets(offset + length, -length); | |
// delete offset | |
offsets.RemoveAt(index); | |
} | |
internal T GetAttribute<T>(RadiusAttributeParser<T> parser, int index) | |
{ | |
// read attribute | |
var offset = OffsetsFromParser(parser)[index]; | |
return parser.Read(buffer, offset + 2, buffer[offset + 1] - 2); | |
} | |
internal void SetAttribute<T>(RadiusAttributeParser<T> parser, int index, T value) | |
{ | |
// check for not read-only | |
EnsureWritable(); | |
// determine offset and old length | |
var offset = OffsetsFromParser(parser)[index]; | |
var oldLength = buffer[offset + 1]; | |
// parse value into byte array | |
var attribute = parser.Write(value); | |
var newLength = attribute.Length + 2; | |
if (newLength > byte.MaxValue) | |
throw new ArgumentOutOfRangeException("value"); | |
// prepare buffer if necessary | |
if (oldLength != newLength) | |
CorrectOffsets(offset + oldLength, newLength - oldLength); | |
// write attribute | |
buffer[offset] = parser.Type; | |
buffer[offset + 1] = (byte)newLength; | |
Array.Copy(attribute, 0, buffer, offset + 2, attribute.Length); | |
} | |
internal int CountAttribute<T>(RadiusAttributeParser<T> parser) | |
{ | |
// return the number of attribute of the given type | |
var offsets = OffsetsFromParser(parser); | |
return offsets.Count; | |
} | |
/// <summary> | |
/// Accesses and modifies attributes within the packet. | |
/// </summary> | |
/// <typeparam name="T">The value type of <paramref name="parser"/>.</typeparam> | |
/// <param name="parser">The attribute parser, either one of <see cref="RadiusAttribute"/> members or a custom one.</param> | |
/// <returns>A modifiable list of all attribute values with the same type as <paramref name="parser"/>.</returns> | |
public RadiusAttributeList<T> Attribute<T>(RadiusAttributeParser<T> parser) { return new RadiusAttributeList<T>(this, parser); } | |
/// <summary> | |
/// Indicates whether the packet is read-only or not. | |
/// </summary> | |
public bool IsReadOnly { get; private set; } | |
/// <summary> | |
/// A packet code specifying the kind of request or response. | |
/// </summary> | |
public PacketCode Code | |
{ | |
get { return (PacketCode)buffer[0]; } | |
private set { buffer[0] = (byte)value; } | |
} | |
/// <summary> | |
/// A single byte identifying the packet. | |
/// </summary> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public byte Identifier | |
{ | |
get { return buffer[1]; } | |
set | |
{ | |
EnsureWritable(); | |
buffer[1] = value; | |
} | |
} | |
/// <summary> | |
/// The current size in bytes of this packet. | |
/// </summary> | |
public int Length | |
{ | |
get { return buffer[2] << 8 | buffer[3]; } | |
private set | |
{ | |
if (value > buffer.Length || value < 20) | |
throw new ArgumentOutOfRangeException("Length"); | |
buffer[2] = (byte)(value >> 8); | |
buffer[3] = (byte)value; | |
} | |
} | |
/// <summary> | |
/// The packet's request or response authenticator. | |
/// </summary> | |
/// <exception cref="ArgumentNullException">The assigned value is <c>null</c>.</exception> | |
/// <exception cref="ArgumentOutOfRangeException">The assigned value is invalid.</exception> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public byte[] Authenticator | |
{ | |
get | |
{ | |
var authenticator = new byte[16]; | |
Array.Copy(buffer, 4, authenticator, 0, 16); | |
return authenticator; | |
} | |
set | |
{ | |
if (value == null) | |
throw new ArgumentNullException("Authenticator"); | |
if (value.Length != 16) | |
throw new ArgumentOutOfRangeException("Authenticator"); | |
EnsureWritable(); | |
Array.Copy(value, 0, buffer, 4, 16); | |
} | |
} | |
/// <summary> | |
/// Creates the default <see cref="Authenticator"/> for a request packet. | |
/// </summary> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <remarks> | |
/// This method does not apply to packets where <see cref="Code"/> equals <see cref="PacketCode.AccessRequest"/>. | |
/// </remarks> | |
/// <exception cref="ArgumentNullException"><paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public void SignRequest(byte[] sharedSecret) | |
{ | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
Sign(EmptyAuthenticator, 0, sharedSecret); | |
} | |
/// <summary> | |
/// Creates the default <see cref="Authenticator"/> for a response packet to a given request. | |
/// </summary> | |
/// <param name="requestPacket">The RADIUS request packet to this response.</param> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <exception cref="ArgumentNullException"><paramref name="requestPacket"/> or <paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public void SignResponse(RadiusPacket requestPacket, byte[] sharedSecret) | |
{ | |
if (requestPacket == null) | |
throw new ArgumentNullException("requestPacket"); | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
Sign(requestPacket.buffer, 4, sharedSecret); | |
} | |
/// <summary> | |
/// Creates the default <see cref="Authenticator"/> for a response packet to a given request. | |
/// </summary> | |
/// <param name="requestPacketAuthenticator">The authenticator of the RADIUS request packet to this response.</param> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <exception cref="ArgumentNullException"><paramref name="requestPacketAuthenticator"/> or <paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
/// <exception cref="ArgumentOutOfRangeException"><paramref name="requestPacketAuthenticator"/> is invalid.</exception> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public void SignResponse(byte[] requestPacketAuthenticator, byte[] sharedSecret) | |
{ | |
if (requestPacketAuthenticator == null) | |
throw new ArgumentNullException("requestPacketAuthenticator"); | |
if (requestPacketAuthenticator.Length != 16) | |
throw new ArgumentOutOfRangeException("requestPacketAuthenticator"); | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
Sign(requestPacketAuthenticator, 0, sharedSecret); | |
} | |
/// <summary> | |
/// Verifies that the packet's <see cref="Authenticator"/> is a valid request authenticator. | |
/// </summary> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <remarks> | |
/// This method does not apply to packets where <see cref="Code"/> equals <see cref="PacketCode.AccessRequest"/>. | |
/// </remarks> | |
/// <returns><c>true</c> if <see cref="Authenticator"/> is value, otherwise <c>false</c>.</returns> | |
/// <exception cref="ArgumentNullException"><paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
public bool VerifyRequest(byte[] sharedSecret) | |
{ | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
return Verify(EmptyAuthenticator, 0, sharedSecret); | |
} | |
/// <summary> | |
/// Verifies that the packet's <see cref="Authenticator"/> is a valid response authenticator. | |
/// </summary> | |
/// <param name="requestPacket">The RADIUS request packet to this response.</param> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <returns><c>true</c> if <see cref="Authenticator"/> is value, otherwise <c>false</c>.</returns> | |
/// <exception cref="ArgumentNullException"><paramref name="requestPacket"/> or <paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
public bool VerifyResponse(RadiusPacket requestPacket, byte[] sharedSecret) | |
{ | |
if (requestPacket == null) | |
throw new ArgumentNullException("requestPacket"); | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
return Verify(requestPacket.buffer, 4, sharedSecret); | |
} | |
/// <summary> | |
/// Verifies that the packet's <see cref="Authenticator"/> is a valid response authenticator. | |
/// </summary> | |
/// <param name="requestPacketAuthenticator">The authenticator of the RADIUS request packet to this response.</param> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <returns><c>true</c> if <see cref="Authenticator"/> is value, otherwise <c>false</c>.</returns> | |
/// <exception cref="ArgumentNullException"><paramref name="requestPacketAuthenticator"/> or <paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
/// <exception cref="ArgumentOutOfRangeException"><paramref name="requestPacketAuthenticator"/> is invalid.</exception> | |
public bool VerifyResponse(byte[] requestPacketAuthenticator, byte[] sharedSecret) | |
{ | |
if (requestPacketAuthenticator == null) | |
throw new ArgumentNullException("requestPacketAuthenticator"); | |
if (requestPacketAuthenticator.Length != 16) | |
throw new ArgumentOutOfRangeException("requestPacketAuthenticator"); | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
return Verify(requestPacketAuthenticator, 0, sharedSecret); | |
} | |
/// <summary> | |
/// Stores the given <paramref name="password"/> as <see cref="RadiusAttribute.UserPassword"/> within the packets attributes, removing any previously existing, and randomizes the <see cref="Authenticator"/>. | |
/// </summary> | |
/// <param name="password">The user password in plain-text.</param> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <exception cref="ArgumentNullException"><paramref name="password"/> or <paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
/// <exception cref="ArgumentOutOfRangeException"><paramref name="password"/> is longer than 128 bytes.</exception> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public void SetUserPassword(string password, byte[] sharedSecret) | |
{ | |
// check all necessary variables | |
if (password == null) | |
throw new ArgumentNullException("password"); | |
var passwordBuffer = Encoding.UTF8.GetBytes(password); | |
if (passwordBuffer.Length > 128) | |
throw new ArgumentOutOfRangeException("password"); | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
// check for not read-only | |
EnsureWritable(); | |
// round the password buffer up to the next 16 bytes boundary | |
var remainder = passwordBuffer.Length % 16; | |
if (remainder > 0) | |
Array.Resize(ref passwordBuffer, passwordBuffer.Length + (16 - remainder)); | |
// set a random authenticator | |
var authenticator = new byte[16]; | |
lock (RNG) | |
RNG.GetBytes(authenticator); | |
Array.Copy(authenticator, 0, buffer, 4, 16); | |
// encode the password | |
using (var md5 = MD5.Create()) | |
{ | |
md5.TransformBlock(sharedSecret, 0, sharedSecret.Length, sharedSecret, 0); | |
md5.TransformFinalBlock(buffer, 4, 16); | |
for (var i = 0; i < passwordBuffer.Length; i += 16) | |
{ | |
var hash = md5.Hash; | |
md5.Initialize(); | |
for (var j = 0; j < 16; j++) | |
passwordBuffer[j + i] ^= hash[j]; | |
md5.TransformBlock(sharedSecret, 0, sharedSecret.Length, sharedSecret, 0); | |
md5.TransformFinalBlock(passwordBuffer, i, 16); | |
} | |
} | |
// actually store the password | |
var attribute = Attribute(RadiusAttribute.UserPassword); | |
switch (attribute.Count) | |
{ | |
case 0: | |
attribute.Add(passwordBuffer); | |
break; | |
case 1: | |
attribute[0] = passwordBuffer; | |
break; | |
default: | |
attribute.Clear(); | |
goto case 0; | |
} | |
} | |
/// <summary> | |
/// Retrieves the password stored within the <see cref="RadiusAttribute.UserPassword"/> attribute. | |
/// </summary> | |
/// <param name="sharedSecret">The shared RADIUS secret.</param> | |
/// <returns>The user password in plain-text.</returns> | |
/// <exception cref="ArgumentNullException"><paramref name="sharedSecret"/> is <c>null</c>.</exception> | |
/// <exception cref="InvalidOperationException">None or more than one <see cref="RadiusAttribute.UserPassword"/> is present.</exception> | |
/// <exception cref="FormatException"><see cref="RadiusAttribute.UserPassword"/> is invalid.</exception> | |
public string GetUserPassword(byte[] sharedSecret) | |
{ | |
// check the necessary variables | |
if (sharedSecret == null) | |
throw new ArgumentNullException("sharedSecret"); | |
var attribute = Attribute(RadiusAttribute.UserPassword); | |
switch (attribute.Count) | |
{ | |
case 0: throw new InvalidOperationException("User-Password is not set"); | |
case 1: break; | |
default: throw new InvalidOperationException("User-Password is not unique"); | |
} | |
var passwordBuffer = attribute[0]; | |
if (passwordBuffer.Length > 128 || passwordBuffer.Length % 16 > 0) | |
throw new FormatException("User-Password is not valid"); | |
// decode the password | |
using (var md5 = MD5.Create()) | |
{ | |
md5.TransformBlock(sharedSecret, 0, sharedSecret.Length, sharedSecret, 0); | |
md5.TransformFinalBlock(buffer, 4, 16); | |
for (var i = 0; i < passwordBuffer.Length; i += 16) | |
{ | |
var hash = md5.Hash; | |
md5.Initialize(); | |
md5.TransformBlock(sharedSecret, 0, sharedSecret.Length, sharedSecret, 0); | |
md5.TransformFinalBlock(passwordBuffer, i, 16); | |
for (var j = 0; j < 16; j++) | |
passwordBuffer[j + i] ^= hash[j]; | |
} | |
} | |
return Encoding.UTF8.GetString(passwordBuffer).TrimEnd('\0'); | |
} | |
/// <summary> | |
/// Retrieves the internal buffer for transmission of the packet. This buffer is not truncated to the length of the packet. | |
/// </summary> | |
/// <returns>The underlying buffer of this RADIUS packet.</returns> | |
/// <exception cref="NotSupportedException">The packet is read-only.</exception> | |
public byte[] GetBuffer() | |
{ | |
EnsureWritable(); | |
return buffer; | |
} | |
} | |
#region Attributes | |
#region Parsers | |
public sealed class RadiusIntegerAttributeParser : RadiusBaseIntegerAttributeParser<int> | |
{ | |
public RadiusIntegerAttributeParser(byte type) : base(type) { } | |
protected override int ReadInt(int rawValue) | |
{ | |
return rawValue; | |
} | |
protected override int WriteInt(int value) | |
{ | |
return value; | |
} | |
} | |
public sealed class RadiusStringAttributeParser : RadiusAttributeParser<string> | |
{ | |
public RadiusStringAttributeParser(byte type) : base(type) { } | |
public override string Read(byte[] buffer, int offset, int length) | |
{ | |
try { return Encoding.UTF8.GetString(buffer, offset, length); } | |
catch (DecoderFallbackException e) { throw new FormatException(string.Format("Attribute {0} contains an invalid string", Type), e); } | |
} | |
public override byte[] Write(string value) | |
{ | |
return Encoding.UTF8.GetBytes(value); | |
} | |
} | |
public sealed class RadiusIPAddressAttributeParser : RadiusAttributeParser<IPAddress> | |
{ | |
private readonly int length; | |
public RadiusIPAddressAttributeParser(byte type, bool isV6 = false) | |
: base(type) | |
{ | |
length = isV6 ? 16 : 4; | |
} | |
public override IPAddress Read(byte[] buffer, int offset, int length) | |
{ | |
if (this.length != length) throw new FormatException(string.Format("Attribute {0} contains an invalid IPv{1} address", Type, this.length % 10)); | |
var ip = new byte[length]; | |
Array.Copy(buffer, offset, ip, 0, length); | |
return new IPAddress(ip); | |
} | |
public override byte[] Write(IPAddress value) | |
{ | |
var buffer = value.GetAddressBytes(); | |
if (buffer.Length != length) | |
throw new ArgumentOutOfRangeException("value"); | |
return buffer; | |
} | |
} | |
public sealed class RadiusBinaryAttributeParser : RadiusAttributeParser<byte[]> | |
{ | |
public RadiusBinaryAttributeParser(byte type) : base(type) { } | |
public override byte[] Read(byte[] buffer, int offset, int length) | |
{ | |
var result = new byte[length]; | |
Array.Copy(buffer, offset, result, 0, length); | |
return result; | |
} | |
public override byte[] Write(byte[] value) | |
{ | |
return value; | |
} | |
} | |
public sealed class RadiusDateTimeAttributeParser : RadiusBaseIntegerAttributeParser<DateTime> | |
{ | |
private readonly static DateTime Epoche = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | |
public RadiusDateTimeAttributeParser(byte type) : base(type) { } | |
protected override DateTime ReadInt(int rawValue) | |
{ | |
return Epoche.AddSeconds(rawValue); | |
} | |
protected override int WriteInt(DateTime value) | |
{ | |
return (int)(value.ToUniversalTime() - Epoche).TotalSeconds; | |
} | |
} | |
public sealed class RadiusEnumAttributeParser<T> : RadiusBaseIntegerAttributeParser<T> where T : struct | |
{ | |
public RadiusEnumAttributeParser(byte type) : base(type) { Debug.Assert(typeof(T).IsEnum); } | |
protected override T ReadInt(int rawValue) | |
{ | |
return (T)Enum.ToObject(typeof(T), rawValue); | |
} | |
protected override int WriteInt(T value) | |
{ | |
return Convert.ToInt32(value); | |
} | |
} | |
public abstract class RadiusBaseIntegerAttributeParser<T> : RadiusAttributeParser<T> | |
{ | |
protected RadiusBaseIntegerAttributeParser(byte type) : base(type) { } | |
protected abstract T ReadInt(int rawValue); | |
protected abstract int WriteInt(T value); | |
public sealed override T Read(byte[] buffer, int offset, int length) | |
{ | |
if (length != 4) throw new FormatException(string.Format("Attribute {0} is invalid", Type)); | |
return ReadInt((buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | (buffer[offset + 3] & 0xff)); | |
} | |
public sealed override byte[] Write(T value) | |
{ | |
var rawValue = WriteInt(value); | |
var buffer = new byte[4]; | |
buffer[0] = (byte)(rawValue >> 24); | |
buffer[1] = (byte)(rawValue >> 16); | |
buffer[2] = (byte)(rawValue >> 8); | |
buffer[3] = (byte)(rawValue); | |
return buffer; | |
} | |
} | |
public abstract class RadiusAttributeParser<T> | |
{ | |
private readonly byte type; | |
protected RadiusAttributeParser(byte type) { this.type = type; } | |
public byte Type { get { return type; } } | |
public abstract T Read(byte[] buffer, int offset, int length); | |
public abstract byte[] Write(T value); | |
} | |
#endregion | |
/// <summary> | |
/// A list of attribute values. | |
/// </summary> | |
/// <typeparam name="T">The value type of underlying <see cref="RadiusAttributeParser{T}"/>.</typeparam> | |
public class RadiusAttributeList<T> : IList<T> | |
{ | |
private readonly RadiusPacket packet; | |
private readonly RadiusAttributeParser<T> parser; | |
internal RadiusAttributeList(RadiusPacket packet, RadiusAttributeParser<T> parser) | |
{ | |
this.packet = packet; | |
this.parser = parser; | |
} | |
public void Insert(int index, T item) { packet.InsertAttribute(parser, index, item); } | |
public void RemoveAt(int index) { packet.RemoveAttribute(parser, index); } | |
public T this[int index] | |
{ | |
get { return packet.GetAttribute(parser, index); } | |
set { packet.SetAttribute(parser, index, value); } | |
} | |
public int Count { get { return packet.CountAttribute(parser); } } | |
public bool IsReadOnly { get { return packet.IsReadOnly; } } | |
#region derived list functions | |
public void AddRange(IEnumerable<T> collection) | |
{ | |
this.InsertRange(Count, collection); | |
} | |
public void InsertRange(int index, IEnumerable<T> collection) | |
{ | |
foreach (var item in collection) | |
Insert(index++, item); | |
} | |
public T[] ToArray() | |
{ | |
var buffer = new T[Count]; | |
for (var i = 0; i < buffer.Length; i++) | |
buffer[i] = packet.GetAttribute(parser, i); | |
return buffer; | |
} | |
public T Peek() | |
{ | |
var position = Count - 1; | |
if (position == -1) | |
throw new InvalidOperationException(); | |
return this[position]; | |
} | |
public void Push(T item) | |
{ | |
Add(item); | |
} | |
public T Pop() | |
{ | |
var position = Count - 1; | |
if (position == -1) | |
throw new InvalidOperationException(); | |
var result = this[position]; | |
RemoveAt(position); | |
return result; | |
} | |
public int IndexOf(T item) | |
{ | |
for (var i = 0; i < Count; i++) | |
if (object.Equals(this[i], item)) | |
return i; | |
return -1; | |
} | |
public void Add(T item) | |
{ | |
Insert(Count, item); | |
} | |
public void Clear() | |
{ | |
for (var i = Count - 1; i >= 0; i--) | |
RemoveAt(i); | |
} | |
public bool Contains(T item) | |
{ | |
return IndexOf(item) != -1; | |
} | |
public void CopyTo(T[] array, int arrayIndex) | |
{ | |
for (var i = 0; i < Count; i++) | |
array[arrayIndex++] = this[i]; | |
} | |
public bool Remove(T item) | |
{ | |
var index = IndexOf(item); | |
if (index == -1) | |
return false; | |
RemoveAt(index); | |
return true; | |
} | |
public IEnumerator<T> GetEnumerator() | |
{ | |
for (var i = 0; i < Count; i++) | |
yield return this[i]; | |
} | |
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
#endregion | |
} | |
/// <summary> | |
/// A static class that defines common RADIUS attributes. | |
/// </summary> | |
public static class RadiusAttribute | |
{ | |
public static readonly RadiusStringAttributeParser UserName = new RadiusStringAttributeParser(1); | |
public static readonly RadiusBinaryAttributeParser UserPassword = new RadiusBinaryAttributeParser(2); | |
public static readonly RadiusBinaryAttributeParser CHAPPassword = new RadiusBinaryAttributeParser(3); | |
public static readonly RadiusIPAddressAttributeParser NASIPAddress = new RadiusIPAddressAttributeParser(4); | |
public static readonly RadiusIntegerAttributeParser NASPort = new RadiusIntegerAttributeParser(5); | |
public static readonly RadiusEnumAttributeParser<ServiceType> ServiceType = new RadiusEnumAttributeParser<ServiceType>(6); | |
public static readonly RadiusEnumAttributeParser<FramedProtocol> FramedProtocol = new RadiusEnumAttributeParser<FramedProtocol>(7); | |
public static readonly RadiusIPAddressAttributeParser FramedIPAddress = new RadiusIPAddressAttributeParser(8); | |
public static readonly RadiusIPAddressAttributeParser FramedIPNetmask = new RadiusIPAddressAttributeParser(9); | |
public static readonly RadiusEnumAttributeParser<FramedRouting> FramedRouting = new RadiusEnumAttributeParser<FramedRouting>(10); | |
public static readonly RadiusStringAttributeParser FilterId = new RadiusStringAttributeParser(11); | |
public static readonly RadiusIntegerAttributeParser FramedMTU = new RadiusIntegerAttributeParser(12); | |
public static readonly RadiusEnumAttributeParser<FramedCompression> FramedCompression = new RadiusEnumAttributeParser<FramedCompression>(13); | |
public static readonly RadiusIPAddressAttributeParser LoginIPHost = new RadiusIPAddressAttributeParser(14); | |
public static readonly RadiusEnumAttributeParser<LoginService> LoginService = new RadiusEnumAttributeParser<LoginService>(15); | |
public static readonly RadiusIntegerAttributeParser LoginTCPPort = new RadiusIntegerAttributeParser(16); | |
public static readonly RadiusStringAttributeParser ReplyMessage = new RadiusStringAttributeParser(18); | |
public static readonly RadiusStringAttributeParser CallbackNumber = new RadiusStringAttributeParser(19); | |
public static readonly RadiusStringAttributeParser CallbackId = new RadiusStringAttributeParser(20); | |
public static readonly RadiusStringAttributeParser FramedRoute = new RadiusStringAttributeParser(22); | |
public static readonly RadiusIPAddressAttributeParser FramedIPXNetwork = new RadiusIPAddressAttributeParser(23); | |
public static readonly RadiusBinaryAttributeParser State = new RadiusBinaryAttributeParser(24); | |
public static readonly RadiusBinaryAttributeParser Class = new RadiusBinaryAttributeParser(25); | |
public static readonly RadiusBinaryAttributeParser VendorSpecific = new RadiusBinaryAttributeParser(26); | |
public static readonly RadiusIntegerAttributeParser SessionTimeout = new RadiusIntegerAttributeParser(27); | |
public static readonly RadiusIntegerAttributeParser IdleTimeout = new RadiusIntegerAttributeParser(28); | |
public static readonly RadiusEnumAttributeParser<TerminationAction> TerminationAction = new RadiusEnumAttributeParser<TerminationAction>(29); | |
public static readonly RadiusStringAttributeParser CalledStationId = new RadiusStringAttributeParser(30); | |
public static readonly RadiusStringAttributeParser CallingStationId = new RadiusStringAttributeParser(31); | |
public static readonly RadiusStringAttributeParser NASIdentifier = new RadiusStringAttributeParser(32); | |
public static readonly RadiusBinaryAttributeParser ProxyState = new RadiusBinaryAttributeParser(33); | |
public static readonly RadiusStringAttributeParser LoginLATService = new RadiusStringAttributeParser(34); | |
public static readonly RadiusStringAttributeParser LoginLATNode = new RadiusStringAttributeParser(35); | |
public static readonly RadiusBinaryAttributeParser LoginLATGroup = new RadiusBinaryAttributeParser(36); | |
public static readonly RadiusIntegerAttributeParser FramedAppleTalkLink = new RadiusIntegerAttributeParser(37); | |
public static readonly RadiusIntegerAttributeParser FramedAppleTalkNetwork = new RadiusIntegerAttributeParser(38); | |
public static readonly RadiusStringAttributeParser FramedAppleTalkZone = new RadiusStringAttributeParser(39); | |
public static readonly RadiusEnumAttributeParser<AccountingStatusType> AcctStatusType = new RadiusEnumAttributeParser<AccountingStatusType>(40); | |
public static readonly RadiusIntegerAttributeParser AcctDelayTime = new RadiusIntegerAttributeParser(41); | |
public static readonly RadiusIntegerAttributeParser AcctInputOctets = new RadiusIntegerAttributeParser(42); | |
public static readonly RadiusIntegerAttributeParser AcctOutputOctets = new RadiusIntegerAttributeParser(43); | |
public static readonly RadiusStringAttributeParser AcctSessionId = new RadiusStringAttributeParser(44); | |
public static readonly RadiusEnumAttributeParser<AccountingAuthentic> AcctAuthentic = new RadiusEnumAttributeParser<AccountingAuthentic>(45); | |
public static readonly RadiusIntegerAttributeParser AcctSessionTime = new RadiusIntegerAttributeParser(46); | |
public static readonly RadiusIntegerAttributeParser AcctInputPackets = new RadiusIntegerAttributeParser(47); | |
public static readonly RadiusIntegerAttributeParser AcctOutputPackets = new RadiusIntegerAttributeParser(48); | |
public static readonly RadiusEnumAttributeParser<AccountingTerminateCause> AcctTerminateCause = new RadiusEnumAttributeParser<AccountingTerminateCause>(49); | |
public static readonly RadiusStringAttributeParser AcctMultiSessionId = new RadiusStringAttributeParser(50); | |
public static readonly RadiusIntegerAttributeParser AcctLinkCount = new RadiusIntegerAttributeParser(51); | |
public static readonly RadiusIntegerAttributeParser AcctInputGigawords = new RadiusIntegerAttributeParser(52); | |
public static readonly RadiusIntegerAttributeParser AcctOutputGigawords = new RadiusIntegerAttributeParser(53); | |
public static readonly RadiusDateTimeAttributeParser EventTimestamp = new RadiusDateTimeAttributeParser(55); | |
public static readonly RadiusBinaryAttributeParser CHAPChallenge = new RadiusBinaryAttributeParser(60); | |
public static readonly RadiusEnumAttributeParser<NasPortType> NASPortType = new RadiusEnumAttributeParser<NasPortType>(61); | |
public static readonly RadiusIntegerAttributeParser PortLimit = new RadiusIntegerAttributeParser(62); | |
public static readonly RadiusIntegerAttributeParser LoginLATPort = new RadiusIntegerAttributeParser(63); | |
public static readonly RadiusIntegerAttributeParser PasswordRetry = new RadiusIntegerAttributeParser(75); | |
public static readonly RadiusEnumAttributeParser<Prompt> Prompt = new RadiusEnumAttributeParser<Prompt>(76); | |
public static readonly RadiusStringAttributeParser ConnectInfo = new RadiusStringAttributeParser(77); | |
public static readonly RadiusBinaryAttributeParser ConfigurationToken = new RadiusBinaryAttributeParser(78); | |
public static readonly RadiusBinaryAttributeParser EAPMessage = new RadiusBinaryAttributeParser(79); | |
public static readonly RadiusBinaryAttributeParser MessageAuthenticator = new RadiusBinaryAttributeParser(80); | |
public static readonly RadiusIntegerAttributeParser AcctInterimInterval = new RadiusIntegerAttributeParser(85); | |
public static readonly RadiusStringAttributeParser NASPortId = new RadiusStringAttributeParser(87); | |
public static readonly RadiusStringAttributeParser FramedPool = new RadiusStringAttributeParser(88); | |
public static readonly RadiusIPAddressAttributeParser NASIPv6Address = new RadiusIPAddressAttributeParser(95, true); | |
public static readonly RadiusBinaryAttributeParser FramedInterfaceId = new RadiusBinaryAttributeParser(96); | |
public static readonly RadiusBinaryAttributeParser FramedIPv6Prefix = new RadiusBinaryAttributeParser(97); | |
public static readonly RadiusIPAddressAttributeParser LoginIPv6Host = new RadiusIPAddressAttributeParser(98, true); | |
public static readonly RadiusStringAttributeParser FramedIPv6Route = new RadiusStringAttributeParser(99); | |
public static readonly RadiusStringAttributeParser FramedIPv6Pool = new RadiusStringAttributeParser(100); | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment