//Processes accounting on logon/logoff public void SessionChange(System.ServiceProcess.SessionChangeDescription changeDescription, pGina.Shared.Types.SessionProperties properties) { if (changeDescription.Reason != System.ServiceProcess.SessionChangeReason.SessionLogon && changeDescription.Reason != System.ServiceProcess.SessionChangeReason.SessionLogoff) { //m_logger.DebugFormat("Not logging on or off for this session change call ({0})... exiting.", changeDescription.Reason); return; } if (properties == null) { //m_logger.DebugFormat("No session properties available. This account does not appear to be managed by pGina. Exiting SessionChange()"); return; } if (!(bool)Settings.Store.EnableAcct) { m_logger.Debug("Session Change stage set on RADIUS plugin but accounting is not enabled in plugin settings."); return; } //Determine username (may change depending on value of UseModifiedName setting) string username = null; UserInformation ui = properties.GetTrackedSingle <UserInformation>(); if (ui == null) { //m_logger.DebugFormat("No userinformation for this session logoff... exiting..."); return; } if ((bool)Settings.Store.UseModifiedName) { username = ui.Username; } else { username = ui.OriginalUsername; } Session session = null; //User is logging on if (changeDescription.Reason == System.ServiceProcess.SessionChangeReason.SessionLogon) { lock (m_sessionManager) { //Check if session information is already available for this id if (!m_sessionManager.Keys.Contains(properties.Id)) { //No session info - must have authed with something other than RADIUS. //m_logger.DebugFormat("RADIUS Accounting Logon: Unable to find session for {0} with GUID {1}", username, properties.Id); if (!(bool)Settings.Store.AcctingForAllUsers) { //m_logger.Debug("Accounting for non-RADIUS users is disabled. Exiting."); return; } RADIUSClient client = GetClient(); session = new Session(properties.Id, username, client); m_sessionManager.Add(properties.Id, session); //Check forced interim-update setting if ((bool)Settings.Store.SendInterimUpdates && (bool)Settings.Store.ForceInterimUpdates) { int interval = (int)Settings.Store.InterimUpdateTime; session.SetInterimUpdate(interval, InterimUpdatesCallback); } } else { session = m_sessionManager[properties.Id]; } } //Determine which plugin authenticated the user (if any) PluginActivityInformation pai = properties.GetTrackedSingle <PluginActivityInformation>(); Packet.Acct_Authentic authSource = Packet.Acct_Authentic.Not_Specified; IEnumerable <Guid> authPlugins = pai.GetAuthenticationPlugins(); Guid LocalMachinePluginGuid = new Guid("{12FA152D-A2E3-4C8D-9535-5DCD49DFCB6D}"); foreach (Guid guid in authPlugins) { if (pai.GetAuthenticationResult(guid).Success) { if (guid == SimpleUuid) { authSource = Packet.Acct_Authentic.RADIUS; } else if (guid == LocalMachinePluginGuid) { authSource = Packet.Acct_Authentic.Local; } else //Not RADIUS, not Local, must be some other auth plugin { authSource = Packet.Acct_Authentic.Remote; } break; } } //We can finally start the accounting process try { lock (session) { session.windowsSessionId = changeDescription.SessionId; //Grab session ID now that we're authenticated session.username = username; //Accting username may have changed depending on 'Use Modified username for accounting option' session.client.startAccounting(username, authSource); //m_logger.DebugFormat("Successfully completed accounting start process..."); } } catch (Exception e) { m_logger.Error("Error occurred while starting accounting.", e); } } //User is logging off else if (changeDescription.Reason == System.ServiceProcess.SessionChangeReason.SessionLogoff) { lock (m_sessionManager) { if (m_sessionManager.Keys.Contains(properties.Id)) { session = m_sessionManager[properties.Id]; } else { //m_logger.DebugFormat("Users {0} is logging off, but no RADIUS session information is available for session ID {1}.", username, properties.Id); return; } //Remove the session from the session manager m_sessionManager.Remove(properties.Id); } lock (session) { //Disbale any active callbacks for this session session.disableCallbacks(); session.active = false; //Assume normal logout if no other terminate reason is listed. if (session.terminate_cause == null) { session.terminate_cause = Packet.Acct_Terminate_Cause.User_Request; } try { //m_logger.DebugFormat("About to send accounting stop packet. Session has been active {0} seconds.", (DateTime.Now - session.client.accountingStartTime).TotalSeconds); session.client.stopAccounting(session.username, session.terminate_cause); } catch (RADIUSException re) { m_logger.DebugFormat("Unable to send accounting stop message for user {0} with ID {1}. Message: {2}", session.username, session.id, re.Message); } } } }
//Sends a start accounting request to the RADIUS server, returns true on acknowledge of request public bool startAccounting(string username, Packet.Acct_Authentic authType) { //Create accounting request packet Packet accountingRequest = new Packet(Packet.Code.Accounting_Request, identifier, sharedKey); accountingRequest.addAttribute(Packet.AttributeType.User_Name, username); accountingRequest.addAttribute(Packet.AttributeType.Acct_Status_Type, (int)Packet.Acct_Status_Type.Start); if (String.IsNullOrEmpty(sessionId)) //Create new guid { sessionId = Guid.NewGuid().ToString(); } accountingRequest.addAttribute(Packet.AttributeType.Acct_Session_Id, sessionId); if (String.IsNullOrEmpty(NAS_Identifier) && NAS_IP_Address == null) { throw new RADIUSException("A NAS_Identifier or NAS_IP_Address (or both) must be supplied."); } if (NAS_IP_Address != null) { accountingRequest.addAttribute(Packet.AttributeType.NAS_IP_Address, NAS_IP_Address); } if (!String.IsNullOrEmpty(NAS_Identifier)) { accountingRequest.addAttribute(Packet.AttributeType.NAS_Identifier, NAS_Identifier); } if (!String.IsNullOrEmpty(called_station_id)) { accountingRequest.addAttribute(Packet.AttributeType.Called_Station_Id, called_station_id); } if (authType != Packet.Acct_Authentic.Not_Specified) { accountingRequest.addAttribute(Packet.AttributeType.Acct_Authentic, (int)authType); } //m_logger.DebugFormat("Attempting to send {0} for user {1}", accountingRequest.code, username); for (int retryCt = 0; retryCt <= maxRetries; retryCt++) { foreach (string server in servers) { //Accounting request packet created, sending data... UdpClient client = new UdpClient(server, accountingPort); client.Client.SendTimeout = timeout; client.Client.ReceiveTimeout = timeout; try { client.Send(accountingRequest.toBytes(), accountingRequest.length); //Listen for response, since the server has been specified, we don't need to re-specify server IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] respBytes = client.Receive(ref RemoteIpEndPoint); Packet responsePacket = new Packet(respBytes); //Verify packet response is good, authenticator should be MD5(Code+ID+Length+RequestAuth+Attributes+Secret) if (!responsePacket.verifyResponseAuthenticator(accountingRequest.authenticator, sharedKey)) { throw new RADIUSException(String.Format("Received response to accounting request with code: {0}, but an incorrect response authenticator was supplied.", responsePacket.code)); } lastReceievedPacket = responsePacket; client.Close(); m_logger.DebugFormat("Received accounting response: {0} for user {1}", responsePacket.code, username); if (responsePacket.code == Packet.Code.Accounting_Response) { accountingStartTime = DateTime.Now; } return(responsePacket.code == Packet.Code.Accounting_Response); } //SocketException is thrown if the server does not respond by end of timeout catch (SocketException se) { m_logger.DebugFormat("Accounting start attempt {0}/{1} using {2} failed. Reason: {3}", retryCt + 1, maxRetries + 1, server, se.Message); } catch (Exception e) { throw new RADIUSException("Unexpected error while trying start accounting.", e); } } } throw new RADIUSException(String.Format("No response from server(s) after {0} tries.", maxRetries + 1)); }