Example #1
0
            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;
            }
Example #2
0
        /// <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));
        }
Example #3
0
        /// <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);
        }
Example #4
0
        /// <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;
            }
        }
Example #5
0
        /// <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);
            }
        }
Example #6
0
 /// <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)));
 }