Commit 6e850f00 authored by Ad Schellevis's avatar Ad Schellevis

(captiveportal/new) add script base, work in progress

parent 93b9605f
#!/usr/local/bin/python2.7
"""
Copyright (c) 2015 Deciso B.V. - Ad Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------------
allow user/host to captive portal
"""
import sys
import ujson
from lib.db import DB
from lib.arp import ARP
from lib.ipfw import IPFW
# parse input parameters
parameters = {'username': '', 'ip_address': None, 'zoneid': None, 'output_type':'plain'}
current_param = None
for param in sys.argv[1:]:
if param[0] == '/':
current_param = param[1:].lower()
elif current_param is not None:
if current_param in parameters:
parameters[current_param] = param.strip()
current_param = None
# create new session
if parameters['ip_address'] is not None and parameters['zoneid'] is not None:
cpDB = DB()
cpIPFW = IPFW()
arp_entry = ARP().get_by_ipaddress(parameters['ip_address'])
if arp_entry is not None:
mac_address = arp_entry['mac']
else:
mac_address = None
response = cpDB.add_client(zoneid=parameters['zoneid'],
username=parameters['username'],
ip_address=parameters['ip_address'],
mac_address=mac_address
)
# check if address is not already registered before adding it to the ipfw table
if not cpIPFW.ip_or_net_in_table(table_number=parameters['zoneid'], address=parameters['ip_address']):
cpIPFW.add_to_table(table_number=parameters['zoneid'], address=parameters['ip_address'])
response['state'] = 'AUTHORIZED'
else:
response = {'state': 'UNKNOWN'}
# output result as plain text or json
if parameters['output_type'] != 'json':
for item in response:
print '%20s %s' % (item, response[item])
else:
print(ujson.dumps(response))
"""
Copyright (c) 2015 Ad Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import tempfile
import subprocess
class ARP(object):
def __init__(self):
""" construct new arp helper
:return: None
"""
self._arp_table = dict()
self._fetch_arp_table()
def _fetch_arp_table(self):
""" parse system arp table and store result in this object
:return: None
"""
# parse arp table
self._arp_table = dict()
with tempfile.NamedTemporaryFile() as output_stream:
subprocess.check_call(['/usr/sbin/arp','-an'], stdout=output_stream, stderr=subprocess.STDOUT)
output_stream.seek(0)
for line in output_stream.read().split('\n'):
if line.find('(') > -1 and line.find(')') > -1:
address = line.split(')')[0].split('(')[-1]
mac = line.split('at')[-1].split('on')[0].strip()
physical_intf = line.split('on')[-1].strip().split(' ')[0]
if address in self._arp_table:
self._arp_table[address]['intf'].append(physical_intf)
else:
self._arp_table[address] = {'mac': mac, 'intf': [physical_intf]}
def list_items(self):
""" return parsed arp list
:return: dict
"""
return self._arp_table
def get_by_ipaddress(self, address):
""" search arp entry by ip address
:param address: ip address
:return: dict or None (if not found)
"""
if address in self._arp_table:
return self._arp_table[address]
else:
return None
"""
Copyright (c) 2015 Ad Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import os
import base64
import time
import sqlite3
class DB(object):
database_filename = '/tmp/captiveportal.sqlite'
def __init__(self):
""" construct new database connection
:return:
"""
self._connection = sqlite3.connect(self.database_filename)
self.create()
def create(self, force_recreate=False):
""" create/initialize new database
:param force_recreate: if database already exists, remove old one first
:return: None
"""
if force_recreate:
if os.path.isfile(self.database_filename):
os.remove(self.database_filename)
self._connection = sqlite3.connect(self.database_filename)
cur = self._connection.cursor()
cur.execute('SELECT count(*) FROM sqlite_master')
if cur.fetchall()[0][0] == 0:
# empty database, initialize database
init_script_filename = '%s/../sql/init.sql' % os.path.dirname(os.path.abspath(__file__))
cur.executescript(open(init_script_filename,'rb').read())
cur.close()
def add_client(self, zoneid, username, ip_address, mac_address):
""" add a new client to the captive portal administration
:param zoneid: cp zone number
:param username: username, maybe empty
:param ip_address: ip address (to unlock)
:param mac_address: physical address of this ip
:return: dictionary with session info
"""
response = dict()
response['zoneid'] = zoneid
response['username'] = username
response['ip_address'] = ip_address
response['mac_address'] = mac_address
response['created'] = time.time() # record creation = sign-in time
response['sessionid'] = base64.b64encode(os.urandom(16)) # generate a new random session id
cur = self._connection.cursor()
# update cp_clients in case there's already a user logged-in at this ip address.
# places an implicit lock on this client.
cur.execute("""update cp_clients
set created = :created
, username = :username
, mac_address = :mac_address
where zoneid = :zoneid
and ip_address = :ip_address
""", response)
# normal operation, new user at this ip, add to host
if cur.rowcount == 0:
cur.execute("""insert into cp_clients(zoneid, sessionid, username, ip_address, mac_address, created)
values (:zoneid, :sessionid, :username, :ip_address, :mac_address, :created)
""",response)
self._connection.commit()
return response
def list_clients(self, zoneid):
""" return list of (administrative) connected clients
:param zoneid: zone id
:return: list of clients
"""
result = list()
fieldnames = list()
cur = self._connection.cursor()
cur.execute("select * from cp_clients where zoneid = :zoneid", {'zoneid': zoneid})
while True:
# fetch field names
if len(fieldnames) == 0:
for fields in cur.description:
fieldnames.append(fields[0])
row = cur.fetchone()
if row is None:
break
else:
record = dict()
for idx in range(len(row)):
record[fieldnames[idx]] = row[idx]
result.append(record)
return result
"""
Copyright (c) 2015 Ad Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
import os
import tempfile
import subprocess
class IPFW(object):
def __init__(self):
pass
def list_table(self, table_number):
""" list ipfw table
:param table_number: ipfw table number
:return: list
"""
DEVNULL = open(os.devnull, 'w')
result = list()
with tempfile.NamedTemporaryFile() as output_stream:
subprocess.check_call(['/sbin/ipfw','table', table_number, 'list'],
stdout=output_stream,
stderr=DEVNULL)
output_stream.seek(0)
for line in output_stream.read().split('\n'):
result.append(line.split(' ')[0])
return result
def ip_or_net_in_table(self, table_number, address):
""" check if address or net is in this zone's table
:param table_number: ipfw table number to query
:param address: ip address or net
:return: boolean
"""
ipfw_tbl = self.list_table(table_number)
if address.find('.') > -1 and address.find('/') == -1:
# address given, search for /32 net in ipfw rules
if '%s/32'%address.strip() in ipfw_tbl:
return True
elif address.strip() in ipfw_tbl:
return True
return False
def add_to_table(self, table_number, address):
""" add new entry to ipfw table
:param table_number: ipfw table number
:param address: ip address or net to add to table
:return:
"""
DEVNULL = open(os.devnull, 'w')
subprocess.call(['/sbin/ipfw', 'table', table_number, 'add', address], stdout=DEVNULL, stderr=DEVNULL)
def delete_from_table(self, table_number, address):
""" remove entry from ipfw table
:param table_number: ipfw table number
:param address: ip address or net to add to table
:return:
"""
DEVNULL = open(os.devnull, 'w')
subprocess.call(['/sbin/ipfw', 'table', table_number, 'delete', address], stdout=DEVNULL, stderr=DEVNULL)
#!/usr/local/bin/python2.7
"""
Copyright (c) 2015 Deciso B.V. - Ad Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------------
return a list of all available rrd files including additional definition data
"""
from lib.arp import ARP
import sys
import ujson
arp_list = ARP().list_items()
if len(sys.argv) > 1 and sys.argv[1].trim().lower() == 'json':
# dump as json
print(ujson.dumps(arp_list))
else:
print ('------------------------- ARP table content -------------------------')
for address in arp_list:
print ('[%10s] %-20s %-20s' % (arp_list[address]['intf'], address, arp_list[address]['mac']))
#!/usr/local/bin/python2.7
"""
Copyright (c) 2015 Deciso B.V. - Ad Schellevis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------------
list connected clients for a captive portal zone
"""
import sys
import ujson
from lib.db import DB
# parse input parameters
parameters = {'zoneid': None, 'output_type':'plain'}
current_param = None
for param in sys.argv[1:]:
if param[0] == '/':
current_param = param[1:].lower()
elif current_param is not None:
if current_param in parameters:
parameters[current_param] = param.strip()
current_param = None
if parameters['zoneid'] is not None:
cpDB = DB()
response = cpDB.list_clients(parameters['zoneid'])
else:
response = []
# output result as plain text or json
if parameters['output_type'] != 'json':
heading = {'sessionid': 'sessionid',
'username': 'username',
'ip_address': 'ip_address',
'mac_address': 'mac_address'
}
print '%(sessionid)-30s %(username)-20s %(ip_address)-20s %(mac_address)-20s' % heading
for item in response:
print '%(sessionid)-30s %(username)-20s %(ip_address)-20s %(mac_address)-20s' % item
else:
print(ujson.dumps(response))
--
-- create new Captive Portal database
--
-- connected clients
create table cp_clients (
zoneid int
, sessionid varchar
, username varchar
, ip_address varchar
, mac_address varchar
, created number
, primary key (zoneid, sessionid)
);
create index cp_clients_ip ON cp_clients (ip_address);
create index cp_clients_zone ON cp_clients (zoneid);
-- session (accounting) info
create table session_info (
zoneid int
, sessionid varchar
, primary key (zoneid, sessionid)
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment