/// <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); } }