public int ServerPos; // Index of the last server a packet was sent to public AuthTransaction(string userName, RadiusPacket packet, int serverPos, DateTime ttr, AsyncResult ar) { this.UserName = userName; this.Packet = packet; this.AsyncResult = ar; this.SendCount = 1; this.ServerPos = serverPos; this.TTR = ttr; }
/// <summary> /// Computes, sets, and returns the response authenticator for the packet. /// </summary> /// <param name="request">The corresponding request packet.</param> /// <param name="secret">The shared secret for the NAS.</param> /// <returns>The computed response authenticator.</returns> /// <remarks> /// This is computed via a MD5 hash over the serialized request /// packet plus the serialized response attributes, plus the /// shared secret. /// </remarks> public byte[] ComputeResponseAuthenticator(RadiusPacket request, string secret) { byte[] serialized = this.ToArray(); byte[] bytes; bytes = Helper.Concat(Helper.Extract(serialized, 0, 4), request.Authenticator); bytes = Helper.Concat(bytes, Helper.Extract(serialized, HeaderSize)); bytes = Helper.Concat(bytes, Helper.ToAnsi(secret)); return(this.Authenticator = MD5Hasher.Compute(bytes)); }
/// <summary> /// Initiates an asynchronous operation to authenticate user credentials. /// </summary> /// <param name="realm">Specifies the authentication scope.</param> /// <param name="account">The account.</param> /// <param name="password">The password.</param> /// <param name="callback">The delegate to be called when the operation completes (or <c>null</c>).</param> /// <param name="state">Application defined state (or <c>null</c>).</param> /// <returns>The <see cref="IAsyncResult" /> instance to be used to track the operation.</returns> /// <remarks> /// <note> /// All successful calls to <see cref="BeginAuthenticate" /> must eventually be /// matched by a call to <see cref="EndAuthenticate" />. /// </note> /// </remarks> public IAsyncResult BeginAuthenticate(string realm, string account, string password, AsyncCallback callback, object state) { AsyncResult arAuth = new AsyncResult(null, callback, state); AuthTransaction transaction; RadiusPacket packet; string userName; int ID; IPEndPoint binding; using (TimedLock.Lock(this)) { userName = Helper.GetUserName(realmFormat, realm, account); ID = GetTransactionID(); if (ID == -1) { throw new RadiusException("RADIUS client cannot track more than 256 simultaneous authentication requests."); } binding = GetServerBinding(ref serverPos); if (binding == NetworkBinding.Any) { throw new RadiusException("None of the RADIUS server hosts resolve to an IP address."); } packet = new RadiusPacket(RadiusCode.AccessRequest, ID, Crypto.Rand(16)); packet.Attributes.Add(new RadiusAttribute(RadiusAttributeType.UserName, userName)); packet.Attributes.Add(new RadiusAttribute(RadiusAttributeType.UserPassword, packet.EncryptUserPassword(password, secret))); packet.Attributes.Add(new RadiusAttribute(RadiusAttributeType.NasIpAddress, nasIPAddress)); transaction = new AuthTransaction(userName, packet, serverPos, SysTime.Now + retryInterval, arAuth);; transactions[ID] = transaction; sock.SendTo(transaction.Packet.ToArray(), binding); arAuth.Result = null; arAuth.Started(); } return(arAuth); }
/// <summary> /// Handles the asynchronous reception of UDP packets on the socket. /// </summary> /// <param name="ar">The <see cref="IAsyncResult" /> instance.</param> private void OnReceive(IAsyncResult ar) { AuthTransaction transaction; RadiusPacket response; byte[] rawPacket; int cbPacket; // Complete receiving the packet and then initiate reception // of the next packet. Note that I don't need a lock here // because there's never more than one async packet receive // outstanding at a given time. try { cbPacket = sock.EndReceiveFrom(ar, ref sourceEP); rawPacket = (byte[])recvBuf.Clone(); sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref sourceEP, onReceive, null); response = new RadiusPacket((IPEndPoint)sourceEP, rawPacket, cbPacket); } catch (SocketClosedException) { // We'll see this when the RADIUS server instance is closed. // I'm not going to report this to the event log. return; } catch (Exception e) { SysLog.LogException(e); return; } // Map the packet to a transaction and signal that the // authentication transaction is complete. using (TimedLock.Lock(this)) { if (!isOpen) { return; } transaction = transactions[response.Identifier]; if (transaction == null) { return; // The transaction must have been aborted } // Validate the response authenticator, discarding the packet // if it's not valid. if (!response.VerifyResponseAuthenticator(transaction.Packet, secret)) { transaction.AsyncResult.Result = false; } else { // Complete the outstanding transaction if the packet is a // valid answer. switch (response.Code) { case RadiusCode.AccessAccept: transaction.AsyncResult.Result = true; break; case RadiusCode.AccessReject: transaction.AsyncResult.Result = false; break; default: return; // Ignore all other answers } } transaction.AsyncResult.Notify(); transactions[response.Identifier] = null; } }
/// <summary> /// Handle asynchronous packet reception. /// </summary> /// <param name="ar">The operation's <see cref="IAsyncResult" /> instance.</param> private void OnReceive(IAsyncResult ar) { RadiusPacket request = null; ServerState state = null; int cbRecv; using (TimedLock.Lock(this)) { // Finish receiving the next request packet try { cbRecv = sock.EndReceiveFrom(ar, ref remoteEP); } catch (SocketClosedException) { // We'll see this when the RADIUS server instance is closed. // I'm not going to report this to the event log. return; } catch (Exception e) { // I'm going to assume that something really bad has // happened to the socket, log the exception and then // return without initiating another receive. This // effectively stops the server. SysLog.LogException(e); return; } // Parse the request. We're going to initiate the // authentication below, outside of the lock. try { request = new RadiusPacket((IPEndPoint)remoteEP, recvBuf, cbRecv); // Unit tests can use this hook to monitor incoming packets // as well cause them to be ignored. if (DiagnosticHook != null && !DiagnosticHook(this, request)) { request = null; } if (request != null && request.Code == RadiusCode.AccessRequest) { state = new ServerState(this); } else { // Ignore all RADIUS requests except for Access-Request request = null; } } catch (Exception e) { SysLog.LogException(e); } // Initiate reception of the next request try { sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref remoteEP, onReceive, null); } catch (Exception e) { SysLog.LogException(e); } } if (request == null) { return; // We're ignoring the packet } // Validate the packet and the NAS RadiusNasInfo nasInfo; IPAddress nasIPAddress; byte[] nasIdentifier; string userName; string realm; string account; byte[] encryptedPassword; string password; if (!request.GetAttributeAsAddress(RadiusAttributeType.NasIpAddress, out nasIPAddress) && !request.GetAttributeAsBinary(RadiusAttributeType.NasIdentifier, out nasIdentifier)) { // Access-Request packets are required by RFC 2865 to have either a NAS-IP-Address // or a NAS-IP-Identifier attribute. Discard any packets that don't have one // of these. return; } if (!request.GetAttributeAsText(RadiusAttributeType.UserName, out userName) || !request.GetAttributeAsBinary(RadiusAttributeType.UserPassword, out encryptedPassword)) { // The User-Name attribute is required by RFC 2865 and this implementation // requires a User-Password attribute. Ignore packets without these. return; } // Parse the realm and account from the user name Helper.ParseUserName(realmFormat, userName, out realm, out account); // Lookup the NAS shared secret and decrypt the password. nasInfo = GetNasInfo(state, request.SourceEP.Address); if (nasInfo == null) { if (defSecret == null) { // Not being able to find information about a NAS device could // represent a serious security problem or attack so I'm going // to log this. Log(state, false, RadiusLogEntryType.UnknownNas, realm, account, "RADIUS: Unknown NAS device NAS=[{0}].", request.SourceEP); return; } nasInfo = new RadiusNasInfo(request.SourceEP.Address, defSecret); } password = request.DecryptUserPassword(encryptedPassword, nasInfo.Secret); // Perform the authentication, compute the response // authenticator and then send a response packet. RadiusPacket response; if (Authenticate(state, realm, account, password)) { Log(state, true, RadiusLogEntryType.Authentication, realm, account, "Authenticated: realm=[{0}] account=[{1}] NAS=[{2}]", realm, account, request.SourceEP); response = new RadiusPacket(RadiusCode.AccessAccept, request.Identifier, null, new RadiusAttribute(RadiusAttributeType.ServiceType, (int)RadiusServiceType.Login)); } else { Log(state, false, RadiusLogEntryType.Authentication, realm, account, "Authentication Fail: realm=[{0}] account=[{1}] NAS=[{2}]", realm, account, request.SourceEP); response = new RadiusPacket(RadiusCode.AccessReject, request.Identifier, null); } response.ComputeResponseAuthenticator(request, nasInfo.Secret); try { sock.SendTo(response.ToArray(), request.SourceEP); } catch (SocketClosedException) { // We'll see this when the RADIUS server instance is closed. // I'm not going to report this to the event log. } catch (Exception e) { SysLog.LogException(e); } }
/// <summary> /// Verifies that the response authenticator in this packet (the response /// packet) is valid for the specified request packet and shared secret. /// </summary> /// <param name="request">The corresponding request packet.</param> /// <param name="secret">The shared secret.</param> /// <returns><c>true</c> if the response authenticator is valid.</returns> public bool VerifyResponseAuthenticator(RadiusPacket request, string secret) { return(Helper.ArrayEquals(this.Authenticator, ComputeResponseAuthenticator(request, secret))); }