diff --git a/src/opnsense/scripts/OPNsense/CaptivePortal/cp-background-process.py b/src/opnsense/scripts/OPNsense/CaptivePortal/cp-background-process.py index 816825d09..1ae97fee4 100755 --- a/src/opnsense/scripts/OPNsense/CaptivePortal/cp-background-process.py +++ b/src/opnsense/scripts/OPNsense/CaptivePortal/cp-background-process.py @@ -33,6 +33,7 @@ import ujson import time import syslog import traceback +import subprocess from lib import Config from lib.db import DB from lib.arp import ARP @@ -190,6 +191,7 @@ def main(): """ Background process loop, runs as backend daemon for all zones. only one should be active at all times. The main job of this procedure is to sync the administration with the actual situation in the ipfw firewall. """ + last_cleanup_timestamp = 0 bgprocess = CPBackgroundProcess() bgprocess.initialize_fixed() @@ -198,6 +200,11 @@ def main(): # open database bgprocess.db.open() + # cleanup old settings, every 5 minutes + if time.time() - last_cleanup_timestamp > 300: + bgprocess.db.cleanup_sessions() + last_cleanup_timestamp = time.time() + # reload cached arp table contents bgprocess.arp.reload() @@ -211,6 +218,9 @@ def main(): # close the database handle while waiting for the next poll bgprocess.db.close() + # process accounting messages (uses php script, for reuse of Auth classes) + subprocess.call(['/usr/local/opnsense/scripts/OPNsense/CaptivePortal/process_accounting_messages.php']) + # sleep time.sleep(5) except KeyboardInterrupt: diff --git a/src/opnsense/scripts/OPNsense/CaptivePortal/lib/db.py b/src/opnsense/scripts/OPNsense/CaptivePortal/lib/db.py index 6c2bef8d1..452cbf8f3 100644 --- a/src/opnsense/scripts/OPNsense/CaptivePortal/lib/db.py +++ b/src/opnsense/scripts/OPNsense/CaptivePortal/lib/db.py @@ -355,3 +355,49 @@ class DB(object): else: self._connection.commit() return 'update' + + def cleanup_sessions(self): + """ cleanup removed sessions, but wait for accounting to finish when busy + """ + cur = self._connection.cursor() + cur.execute(""" delete + from cp_clients + where cp_clients.deleted = 1 + and not exists ( + select 1 + from accounting_state + where accounting_state.zoneid = cp_clients.zoneid + and accounting_state.sessionid = cp_clients.sessionid + and accounting_state.state <> 'STOPPED' + ) + """) + cur.execute(""" delete + from accounting_state + where not exists ( + select 1 + from cp_clients + where cp_clients.zoneid = accounting_state.zoneid + and cp_clients.sessionid = accounting_state.sessionid + ) + """) + cur.execute(""" delete + from session_info + where not exists ( + select 1 + from cp_clients + where session_info.zoneid = cp_clients.zoneid + and session_info.sessionid = cp_clients.sessionid + ) + """) + + cur.execute(""" delete + from session_restrictions + where not exists ( + select 1 + from cp_clients + where session_restrictions.zoneid = cp_clients.zoneid + and session_restrictions.sessionid = cp_clients.sessionid + ) + """) + + self._connection.commit() diff --git a/src/opnsense/scripts/OPNsense/CaptivePortal/process_accounting_messages.php b/src/opnsense/scripts/OPNsense/CaptivePortal/process_accounting_messages.php new file mode 100755 index 000000000..cb42537d8 --- /dev/null +++ b/src/opnsense/scripts/OPNsense/CaptivePortal/process_accounting_messages.php @@ -0,0 +1,96 @@ +#!/usr/local/bin/php +query(' + select c.zoneid + , c.sessionid + , c.username + , c.authenticated_via + , c.deleted + , c.created + , accs.state + from cp_clients c + inner join session_restrictions sr on sr.zoneid = c.zoneid and sr.sessionid = c.sessionid + left join session_info si on c.zoneid = si.zoneid and c.sessionid = si.sessionid + left join accounting_state accs on accs.zoneid = c.zoneid and accs.sessionid = c.sessionid + order by c.authenticated_via + '); + +// process all sessions +while($row = $result->fetchArray(SQLITE3_ASSOC) ){ + $authFactory = new OPNsense\Auth\AuthenticationFactory; + $authenticator = $authFactory->get($row['authenticated_via']); + if ($authenticator != null) { + if ($row['state'] == null) { + // new accounting state, send start event (if applicable) + $stmt = $db->prepare('insert into accounting_state(zoneid, sessionid, state) + values (:zoneid, :sessionid, \'RUNNING\')'); + $stmt->bindParam(':zoneid', $row['zoneid']); + $stmt->bindParam(':sessionid', $row['sessionid']); + $stmt->execute(); + if (method_exists($authenticator,'startAccounting')) { + // send start accounting event + $authenticator->startAccounting($row['username'], $row['sessionid']); + } + } elseif ($row['deleted'] == 1 && $row['state'] != 'STOPPED') { + // stop accounting, send stop event (if applicable) + $stmt = $db->prepare('update accounting_state + set state = \'STOPPED\' + where zoneid = :zoneid + and sessionid = :sessionid'); + $stmt->bindParam(':zoneid', $row['zoneid']); + $stmt->bindParam(':sessionid', $row['sessionid']); + $stmt->execute(); + if (method_exists($authenticator,'startAccounting')) { + + $time_spend = time() - $row['created']; + $authenticator->stopAccounting($row['username'], $row['sessionid'], $time_spend); + } + } elseif ($row['state'] != 'STOPPED') { + // send interim updates (if applicable) + if (method_exists($authenticator,'updateAccounting')) { + // send interim update event + $time_spend = time() - $row['created']; + $authenticator->updateAccounting($row['username'], $row['sessionid'], $time_spend); + } + } + } +} +$db->close();