├── .gitignore ├── Dockerfile ├── README.md ├── _config.yml ├── db ├── __init__.py ├── influx.py └── influx_analytics.py ├── environment.yml ├── location_utils ├── BSSIDApple.proto ├── BSSIDApple_pb2.py ├── GSM.proto ├── GSM_pb2.py ├── __init__.py ├── wigle_lib.py ├── wigle_query.py └── wloc.py ├── mac_parser ├── README.md ├── __init__.py ├── manuf └── manuf.py ├── main.py ├── manage.py ├── packet_processing ├── __init__.py └── packet_processor.py ├── run.sh ├── security_ssid ├── __init__.py ├── admin.py ├── asgi.py ├── models.py ├── settings.py ├── static │ ├── ajax-loader.gif │ ├── bluff-min.js │ ├── css │ │ └── main.css │ ├── flag.png │ ├── jquery-2.1.1.min.js │ ├── js-class.js │ ├── locate.png │ └── wifi.png ├── templates │ ├── ap_detail.html │ ├── ap_list.html │ ├── apple-mobile-ajax.js │ ├── apple-mobile.html │ ├── apple-wloc-ajax.js │ ├── apple-wloc.html │ ├── base.html │ ├── client_detail.html │ ├── client_list.html │ ├── home.html │ ├── stats.html │ └── wigle-wloc.html ├── templatetags │ ├── __init__.py │ ├── dict_lookup.py │ ├── dot_colour.py │ └── urldecode.py ├── urls.py ├── views.py └── wsgi.py └── tests ├── influxdb_test.py └── wigle_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | security_ssid/settings.py 2 | .idea/ 3 | db.sqlite3 4 | color.pyc 5 | security_ssid/BSSIDApple_pb2.pyc 6 | security_ssid/GSM_pb2.pyc 7 | security_ssid/__init__.pyc 8 | security_ssid/admin.pyc 9 | security_ssid/models.pyc 10 | security_ssid/settings.pyc 11 | security_ssid/templatetags/__init__.pyc 12 | security_ssid/templatetags/dict_lookup.pyc 13 | security_ssid/templatetags/urldecode.pyc 14 | security_ssid/urls.pyc 15 | security_ssid/views.pyc 16 | security_ssid/wigle_api.pyc 17 | security_ssid/wloc.pyc 18 | security_ssid/wsgi.pyc 19 | wigle_lib.pyc 20 | /monitor_results.sqlite3 21 | sample-data.cap -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kalilinux/kali-linux-docker 2 | RUN apt clean all && apt update && apt upgrade -y 3 | RUN apt install -y aircrack-ng pciutils 4 | RUN apt autoremove -y && apt clean all 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Security SSID ABI (SSID WiFi Listener) 2 | ========== 3 | 4 | Using a monitor-mode 2.4Ghz receiver, this Django app displays data that is catalogued from passively sniffing on SSID probes, ARPs, and MDNS (Bonjour) packets that are being broadcast by nearby wireless devices. 5 | 6 | Some devices transmit ARPs, which sometimes contain MAC addresses (BSSIDs) of previously joined WiFi networks, as described in [[1]][ars]. This system captures these ARPs and displays them. 7 | 8 | Components 9 | ---------- 10 | 11 | 2 major components and further python modules: 12 | 13 | * main.py uses [Scapy](http://www.secdev.org/projects/scapy/) to extract data from a live capture (via airmon-ng) or pcap file, and inserts this data into 2 databases: Client Summary and Access Point summary data is loaded into a SQLite or Postgres DB (managed by Django), which is the data that is displayed in the Django web app. 14 | 15 | Beyond the summary Client Data, all 802.11 (aka Dot11) packet summaries are loaded into a second database: InfluxDB 1.8. 16 | 17 | * A Django web app provides an interface to view and analyse the data. 18 | This includes views of: 19 | 20 | 1. All detected devices and the SSIDs / BSSIDs each has probed 21 | 1. A view by network 22 | 1. A view showing a breakdown of the most popular device manufacturers, based on client MAC address Ethernet OUIs 23 | 24 | * ./location_utils/wloc.py provides a _QueryBSSID()_ function which looks up a given BSSID (AP MAC address) on Apple's WiFi location service. It will return the coordinates of the MAC queried for and usually an additional 400 nearby BSSIDs and their coordinates. 25 | 26 | * ./location_utils/wigle_lib.py provides a _getLocation()_ function for querying a given SSID on the wigle.net database and returns GPS coordinates. Note: It must be configured with a valid username and password set in the settings.py file. Please respect the wigle.net ToS in using this module. This project-specific library has been created to work with the new Wigle API (V2: https://api.wigle.net/swagger#/Network_search_and_information_tools). Big thanks to the Wigle team for their great support and allowing this project to use their data. 27 | 28 | *** Instructions 29 | ------------ 30 | Install Anaconda 3 for Linux: https://www.anaconda.com/products/individual#linux 31 | 32 | ``` 33 | git clone git@github.com:GISDev01/security-ssid-abi.git 34 | cd security-ssid-abi 35 | conda env create -f environment.yml 36 | source activate securityssidabi38 37 | 38 | # Initialize the initial Django DB 39 | ./manage.py migrate --run-syncdb 40 | ./manage.py createsuperuser 41 | # Create creds to log in to the /admin Web GUI endpoint) 42 | 43 | # Start the web interface by running 44 | # (change 127.0.0.1 to any IP for the Django web server to listen on) 45 | ./manage.py runserver 127.0.0.1:8000 46 | 47 | ``` 48 | 49 | # To sniff traffic 50 | ``` 51 | sudo apt install aircrack-ng -y && sudo apt install git -y && sudo apt install libpq-dev 52 | # We can only run the sniffer as root, because it opens a raw socket (via scapy sniff) 53 | sudo -i 54 | ``` 55 | 56 | Bring up a wifi interface in monitor mode (usually mon0) so that airodump-ng shows traffic. 57 | 58 | `sudo airmon-ng check kill` 59 | 60 | Note: check what the connected wireless NIC device is named using iwconfig 61 | 62 | `iwconfig` 63 | 64 | Make sure the USB wireless NIC, such as an Alfa AWUS036 is passed-through to the VM 65 | Example value is: wlx00c0ca4f55b9 (or it could be something like wlan0) 66 | 67 | `sudo airmon-ng start wlx00c0ca4f55b9` 68 | 69 | - Sometimes the OS and Wireless card like to act up and display a message like: "SIOCSIFFLAGS: Operation not possible due to RF-kill". In that case, this can help: 70 | `sudo rfkill unblock wifi; sudo rfkill unblock all` 71 | 72 | 4. Optional (set to false by default in setting.py). Get InfluxDB up and running, and update the .\security_ssid\settings.py with the correct IP or hostname of the InfluxDB box. 73 | 74 | Note: Fastest way to get it up and running for development is with Docker: 75 | 76 | `docker run -p 8086:8086 influxdb:1.8.0` 77 | 78 | 5. Start live sniffing with: 79 | 80 | `./run.sh -i mon0` 81 | 82 | (Note: the -i param here is to identify the interface name that airmon-ng is monitoring packets with, default value is actually mon0) 83 | 84 | 85 | Optional: To solicit ARPs from iOS devices, set up an access point with DHCP disabled (e.g. using airbase-ng) and configure your sniffing interface to the same channel. 86 | Once associated, iOS devices will send up to three ARPs destined for the MAC address of the DHCP server on previously joined networks. On typical home WiFi routers, the DHCP server MAC address is the same as the WiFi interface MAC address, which can be used for accurate geolocation. 87 | 88 | Optional: For debugging code locally, a .pcap (in this case, .cap) file can be generated with (as root or with sudo): 89 | 90 | `airodump-ng -w sample-data --output-format pcap mon0` 91 | 92 | Then you can run with (assuming sample-data.cap is in the root of this repo): 93 | 94 | `./run.sh -r sample-data.cap` 95 | 96 | To run Postgres in Docker for testing, as an alternative to sqlite 97 | ``` 98 | docker run -d -p 5432:5432 --name postgres95 -e POSTGRES_PASSWORD=postgres postgres:9.5 99 | ``` 100 | If needed, get in to the box with: 101 | 102 | `docker exec -it postgres95 bash` 103 | 104 | `psql -U postgres` 105 | 106 | 107 | Dependencies 108 | ------------------------------------------------------------------------------------------------------------ 109 | See requirements.txt for python modules and versions required. 110 | Externally, this application writes out to an InfluxDB data store (in addition to the local Django DB (sqlite)). 111 | 112 | This repo has been recently developed on a Ubuntu 16.04 (64-bit) VM with Python 3.8, Django 4.x and Scapy 2.4.x. 113 | The web interface code has been updated and tested with Django running on Mac OS X Sierra with Python 3.8. 114 | 115 | Network sniffing via airmon-ng has been tested on a Ubuntu 16.04 VM and Raspian (RasPi 3). 116 | 117 | Credits 118 | ------------------------------------------------------------------------------------------------------------ 119 | ------------------------------------------------------------------------------------------------------------ 120 | This repo was originally written by @hubert3 / hubert(at)pentest.com. Presented at Blackhat USA July 2012, the original code published on Github 2012-08-31. 121 | The implementation of wloc.py is based on work by François-Xavier Aguessy and Côme Demoustier [[2]][paper]. 122 | Mark Wuergler of Immunity, Inc. provided helpful information through mailing list posts and Twitter replies. 123 | Includes Bluff JS chart library by James Coglan. 124 | 1. http://arstechnica.com/apple/2012/03/anatomy-of-an-iphone-leak/ 125 | 2. http://fxaguessy.fr/rapport-pfe-interception-ssl-analyse-donnees-localisation-smartphones/ 126 | [ars]: http://arstechnica.com/apple/2012/03/anatomy-of-an-iphone-leak/ 127 | [paper]: http://fxaguessy.fr/rapport-pfe-interception-ssl-analyse-donnees-localisation-smartphones/ 128 | 129 | (gisdev01) Starting in mid-2017 and then again in 2020, several updates and upgrades have been completed, including addition of InfluxDB functionality, summary functionality, Raspberry Pi support, and several front-end updates. 130 | 131 | 132 | 133 | ``` 134 | conda install Django 135 | conda install matplotlib 136 | conda install -c conda-forge influxdb 137 | conda install -c conda-forge netaddr 138 | conda install -c conda-forge google-api-core 139 | 140 | # Not available in any conda channels 141 | pip install django-picklefield 142 | 143 | conda env export > environment.yml 144 | conda env create -f environment.yml 145 | 146 | 147 | 148 | 149 | ``` -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /db/__init__.py: -------------------------------------------------------------------------------- 1 | from influxdb import InfluxDBClient 2 | 3 | import security_ssid.settings as settings 4 | 5 | influxdb_client = InfluxDBClient(settings.INFLUX_HOST, 6 | settings.INFLUX_PORT, 7 | settings.INFLUX_USER, 8 | settings.INFLUX_PASSWORD, 9 | settings.INFLUX_DB, 10 | timeout=settings.INFLUX_TIMEOUT_SEC) 11 | 12 | influxdb_client.create_database(settings.INFLUX_DB) 13 | -------------------------------------------------------------------------------- /db/influx.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from db import influxdb_client 4 | 5 | 6 | def assemble_json(measurement, pkt_timestamp, rssi_value, client_mac_addr, probed_ssid): 7 | return { 8 | "measurement": measurement, 9 | "tags": { 10 | "clientmac": client_mac_addr, 11 | "probedssid": probed_ssid, 12 | "location": "001", 13 | "sensor": "001" 14 | }, 15 | "time": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.localtime(int(pkt_timestamp))), 16 | "fields": { 17 | "rssi": rssi_value 18 | } 19 | } 20 | 21 | 22 | def write_data(data_points): 23 | influxdb_client.write_points([data_points]) 24 | -------------------------------------------------------------------------------- /db/influx_analytics.py: -------------------------------------------------------------------------------- 1 | from db import influxdb_client 2 | 3 | query = """ 4 | SHOW TAG VALUES FROM "clientdevices" WITH KEY IN ("probedssid") 5 | """ 6 | 7 | result_set = influxdb_client.query(query) 8 | print("Result: {0}".format(result_set)) 9 | 10 | for result in result_set: 11 | ssid_list = [ssid['value'] for ssid in result] 12 | 13 | print('SSID List: {}'.format(ssid_list)) 14 | 15 | 16 | ########################################################################## 17 | # Loop through all of the detected SSID names in the DB, for all timeframes 18 | for ssid in ssid_list: 19 | ssid = "'" + ssid.replace("'", r"\'") + "'" 20 | ssid_query = 'SELECT * FROM "clientdevices" WHERE "probedssid" = ' + ssid 21 | #print(ssid_query) 22 | #ssid_results = influxdb_client.query(ssid_query) 23 | #print("ssid_results: {0}".format(ssid_results)) 24 | 25 | 26 | ############################################## 27 | # Hardcoded test for dynamic MEAN calc on RSSI 28 | ssid_name = 'linksys' 29 | ssid_name = "'" + ssid_name.replace("'", r"\'") + "'" 30 | 31 | field_name = 'rssi' 32 | mean_field = field_name.replace("'", r"\'") 33 | 34 | ssid_test_query_mean = 'SELECT MEAN("' + mean_field + '") ' \ 35 | 'FROM "clientdevices" ' \ 36 | 'WHERE "probedssid" = ' + ssid_name 37 | print(ssid_test_query_mean) 38 | 39 | ssid_test_results = influxdb_client.query(ssid_test_query_mean) 40 | print("Mean RRSI: {} for SSID: {}".format(ssid_test_results, ssid_name)) 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: securityssidabi38 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - aiohttp=3.8.3=py38hef030d1_1 7 | - aiosignal=1.3.1=pyhd8ed1ab_0 8 | - async-timeout=4.0.2=pyhd8ed1ab_0 9 | - attrs=22.1.0=pyh71513ae_1 10 | - brotlipy=0.7.0=py38hef030d1_1005 11 | - cachetools=5.2.0=pyhd8ed1ab_0 12 | - cffi=1.14.6=py38h9688ba1_0 13 | - charset-normalizer=2.1.1=pyhd8ed1ab_0 14 | - cryptography=38.0.3=py38ha6c3189_0 15 | - frozenlist=1.3.3=py38hef030d1_0 16 | - google-api-core=2.10.2=pyhd8ed1ab_0 17 | - google-auth=2.14.1=pyh1a96a4e_0 18 | - googleapis-common-protos=1.57.0=py38h50d1736_0 19 | - idna=3.4=pyhd8ed1ab_0 20 | - influxdb=5.3.1=pyhd3deb0d_0 21 | - libprotobuf=3.21.9=hbc0c0cd_0 22 | - libzlib=1.2.13=hfd90126_4 23 | - msgpack-python=1.0.4=py38h98b9b1b_1 24 | - multidict=6.0.2=py38hef030d1_2 25 | - netaddr=0.7.19=py_0 26 | - protobuf=4.21.9=py38h4cd09af_0 27 | - pyasn1=0.4.8=py_0 28 | - pyasn1-modules=0.2.7=py_0 29 | - pycparser=2.21=pyhd8ed1ab_0 30 | - pyopenssl=22.1.0=pyhd8ed1ab_0 31 | - pysocks=1.7.1=py38h50d1736_5 32 | - python-dateutil=2.8.2=pyhd8ed1ab_0 33 | - python_abi=3.8=2_cp38 34 | - pytz=2022.6=pyhd8ed1ab_0 35 | - pyu2f=0.1.5=pyhd8ed1ab_0 36 | - requests=2.28.1=pyhd8ed1ab_1 37 | - rsa=4.9=pyhd8ed1ab_0 38 | - six=1.16.0=pyh6c4a22f_0 39 | - typing-extensions=4.4.0=hd8ed1ab_0 40 | - typing_extensions=4.4.0=pyha770c72_0 41 | - urllib3=1.26.11=pyhd8ed1ab_0 42 | - yarl=1.8.1=py38hef030d1_0 43 | - asgiref=3.5.2=py38hecd8cb5_0 44 | - backports=1.1=pyhd3eb1b0_0 45 | - backports.zoneinfo=0.2.1=py38hca72f7f_0 46 | - blas=1.0=mkl 47 | - brotli=1.0.9=hca72f7f_7 48 | - brotli-bin=1.0.9=hca72f7f_7 49 | - ca-certificates=2022.10.11=hecd8cb5_0 50 | - certifi=2022.9.24=py38hecd8cb5_0 51 | - cycler=0.11.0=pyhd3eb1b0_0 52 | - django=4.1=py38hecd8cb5_0 53 | - fonttools=4.25.0=pyhd3eb1b0_0 54 | - freetype=2.12.1=hd8bbffd_0 55 | - giflib=5.2.1=haf1e3a3_0 56 | - intel-openmp=2021.4.0=hecd8cb5_3538 57 | - jpeg=9e=hca72f7f_0 58 | - kiwisolver=1.4.2=py38he9d5cce_0 59 | - lcms2=2.12=hf1fd2bf_0 60 | - lerc=3.0=he9d5cce_0 61 | - libbrotlicommon=1.0.9=hca72f7f_7 62 | - libbrotlidec=1.0.9=hca72f7f_7 63 | - libbrotlienc=1.0.9=hca72f7f_7 64 | - libcxx=14.0.6=h9765a3e_0 65 | - libdeflate=1.8=h9ed2024_5 66 | - libffi=3.3=hb1e8313_2 67 | - libpng=1.6.37=ha441bb4_0 68 | - libtiff=4.4.0=h2cd0358_2 69 | - libwebp=1.2.4=h56c3ce4_0 70 | - libwebp-base=1.2.4=hca72f7f_0 71 | - lz4-c=1.9.3=h23ab428_1 72 | - matplotlib=3.5.3=py38hecd8cb5_0 73 | - matplotlib-base=3.5.3=py38hfb0c5b7_0 74 | - mkl=2021.4.0=hecd8cb5_637 75 | - mkl-service=2.4.0=py38h9ed2024_0 76 | - mkl_fft=1.3.1=py38h4ab4a9b_0 77 | - mkl_random=1.2.2=py38hb2f4e1b_0 78 | - munkres=1.1.4=py_0 79 | - ncurses=6.3=hca72f7f_3 80 | - numpy=1.23.4=py38he696674_0 81 | - numpy-base=1.23.4=py38h9cd3388_0 82 | - openssl=1.1.1s=hca72f7f_0 83 | - packaging=21.3=pyhd3eb1b0_0 84 | - pillow=9.2.0=py38hde71d04_1 85 | - pip=22.2.2=py38hecd8cb5_0 86 | - pyparsing=3.0.9=py38hecd8cb5_0 87 | - python=3.8.15=h4319210_0 88 | - readline=8.2=hca72f7f_0 89 | - setuptools=65.5.0=py38hecd8cb5_0 90 | - sqlite=3.39.3=h707629a_0 91 | - sqlparse=0.4.3=py38hecd8cb5_0 92 | - tk=8.6.12=h5d9f67b_0 93 | - tornado=6.2=py38hca72f7f_0 94 | - tzdata=2022f=h04d1e81_0 95 | - wheel=0.37.1=pyhd3eb1b0_0 96 | - xz=5.2.6=hca72f7f_0 97 | - zlib=1.2.13=h4dc903c_0 98 | - zstd=1.5.2=hcb37349_0 99 | - pip: 100 | - django-picklefield 101 | -------------------------------------------------------------------------------- /location_utils/BSSIDApple.proto: -------------------------------------------------------------------------------- 1 | message WifiDetected { 2 | required string bssid = 1; 3 | message Location { 4 | optional int64 latitude = 1; 5 | optional int64 longitude = 2; 6 | optional int64 valeur_inconnue3 = 3; 7 | optional int64 valeur_inconnue4 = 4; 8 | optional int64 valeur_inconnue5 = 5; 9 | optional int64 valeur_inconnue6 = 6; 10 | optional int64 valeur_inconnue7 = 7; 11 | optional int64 valeur_inconnue8 = 8; 12 | optional int64 valeur_inconnue9 = 9; 13 | optional int64 valeur_inconnue10 = 10; 14 | optional int64 valeur_inconnue11 = 11; 15 | optional int64 valeur_inconnue12 = 12; 16 | optional int64 valeur_inconnue21 = 21; 17 | } 18 | optional Location location= 2; 19 | } 20 | 21 | message BlockBSSIDApple { 22 | optional int64 valeur_inconnue0 = 1; 23 | repeated WifiDetected wifi = 2; 24 | optional int32 valeur_inconnue1 = 3; 25 | optional int32 valeur_inconnue2 = 4; 26 | optional string APIName = 5; 27 | } 28 | -------------------------------------------------------------------------------- /location_utils/BSSIDApple_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: BSSIDApple.proto 3 | 4 | import sys 5 | _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | from google.protobuf import descriptor_pb2 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | 17 | 18 | DESCRIPTOR = _descriptor.FileDescriptor( 19 | name='BSSIDApple.proto', 20 | package='', 21 | serialized_pb=_b('\n\x10\x42SSIDApple.proto\"\x9b\x03\n\x0cWifiDetected\x12\r\n\x05\x62ssid\x18\x01 \x02(\t\x12(\n\x08location\x18\x02 \x01(\x0b\x32\x16.WifiDetected.Location\x1a\xd1\x02\n\x08Location\x12\x10\n\x08latitude\x18\x01 \x01(\x03\x12\x11\n\tlongitude\x18\x02 \x01(\x03\x12\x18\n\x10valeur_inconnue3\x18\x03 \x01(\x03\x12\x18\n\x10valeur_inconnue4\x18\x04 \x01(\x03\x12\x18\n\x10valeur_inconnue5\x18\x05 \x01(\x03\x12\x18\n\x10valeur_inconnue6\x18\x06 \x01(\x03\x12\x18\n\x10valeur_inconnue7\x18\x07 \x01(\x03\x12\x18\n\x10valeur_inconnue8\x18\x08 \x01(\x03\x12\x18\n\x10valeur_inconnue9\x18\t \x01(\x03\x12\x19\n\x11valeur_inconnue10\x18\n \x01(\x03\x12\x19\n\x11valeur_inconnue11\x18\x0b \x01(\x03\x12\x19\n\x11valeur_inconnue12\x18\x0c \x01(\x03\x12\x19\n\x11valeur_inconnue21\x18\x15 \x01(\x03\"\x8d\x01\n\x0f\x42lockBSSIDApple\x12\x18\n\x10valeur_inconnue0\x18\x01 \x01(\x03\x12\x1b\n\x04wifi\x18\x02 \x03(\x0b\x32\r.WifiDetected\x12\x18\n\x10valeur_inconnue1\x18\x03 \x01(\x05\x12\x18\n\x10valeur_inconnue2\x18\x04 \x01(\x05\x12\x0f\n\x07\x41PIName\x18\x05 \x01(\t') 22 | ) 23 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 24 | 25 | 26 | 27 | 28 | _WIFIDETECTED_LOCATION = _descriptor.Descriptor( 29 | name='Location', 30 | full_name='WifiDetected.Location', 31 | filename=None, 32 | file=DESCRIPTOR, 33 | containing_type=None, 34 | fields=[ 35 | _descriptor.FieldDescriptor( 36 | name='latitude', full_name='WifiDetected.Location.latitude', index=0, 37 | number=1, type=3, cpp_type=2, label=1, 38 | has_default_value=False, default_value=0, 39 | message_type=None, enum_type=None, containing_type=None, 40 | is_extension=False, extension_scope=None, 41 | options=None), 42 | _descriptor.FieldDescriptor( 43 | name='longitude', full_name='WifiDetected.Location.longitude', index=1, 44 | number=2, type=3, cpp_type=2, label=1, 45 | has_default_value=False, default_value=0, 46 | message_type=None, enum_type=None, containing_type=None, 47 | is_extension=False, extension_scope=None, 48 | options=None), 49 | _descriptor.FieldDescriptor( 50 | name='valeur_inconnue3', full_name='WifiDetected.Location.valeur_inconnue3', index=2, 51 | number=3, type=3, cpp_type=2, label=1, 52 | has_default_value=False, default_value=0, 53 | message_type=None, enum_type=None, containing_type=None, 54 | is_extension=False, extension_scope=None, 55 | options=None), 56 | _descriptor.FieldDescriptor( 57 | name='valeur_inconnue4', full_name='WifiDetected.Location.valeur_inconnue4', index=3, 58 | number=4, type=3, cpp_type=2, label=1, 59 | has_default_value=False, default_value=0, 60 | message_type=None, enum_type=None, containing_type=None, 61 | is_extension=False, extension_scope=None, 62 | options=None), 63 | _descriptor.FieldDescriptor( 64 | name='valeur_inconnue5', full_name='WifiDetected.Location.valeur_inconnue5', index=4, 65 | number=5, type=3, cpp_type=2, label=1, 66 | has_default_value=False, default_value=0, 67 | message_type=None, enum_type=None, containing_type=None, 68 | is_extension=False, extension_scope=None, 69 | options=None), 70 | _descriptor.FieldDescriptor( 71 | name='valeur_inconnue6', full_name='WifiDetected.Location.valeur_inconnue6', index=5, 72 | number=6, type=3, cpp_type=2, label=1, 73 | has_default_value=False, default_value=0, 74 | message_type=None, enum_type=None, containing_type=None, 75 | is_extension=False, extension_scope=None, 76 | options=None), 77 | _descriptor.FieldDescriptor( 78 | name='valeur_inconnue7', full_name='WifiDetected.Location.valeur_inconnue7', index=6, 79 | number=7, type=3, cpp_type=2, label=1, 80 | has_default_value=False, default_value=0, 81 | message_type=None, enum_type=None, containing_type=None, 82 | is_extension=False, extension_scope=None, 83 | options=None), 84 | _descriptor.FieldDescriptor( 85 | name='valeur_inconnue8', full_name='WifiDetected.Location.valeur_inconnue8', index=7, 86 | number=8, type=3, cpp_type=2, label=1, 87 | has_default_value=False, default_value=0, 88 | message_type=None, enum_type=None, containing_type=None, 89 | is_extension=False, extension_scope=None, 90 | options=None), 91 | _descriptor.FieldDescriptor( 92 | name='valeur_inconnue9', full_name='WifiDetected.Location.valeur_inconnue9', index=8, 93 | number=9, type=3, cpp_type=2, label=1, 94 | has_default_value=False, default_value=0, 95 | message_type=None, enum_type=None, containing_type=None, 96 | is_extension=False, extension_scope=None, 97 | options=None), 98 | _descriptor.FieldDescriptor( 99 | name='valeur_inconnue10', full_name='WifiDetected.Location.valeur_inconnue10', index=9, 100 | number=10, type=3, cpp_type=2, label=1, 101 | has_default_value=False, default_value=0, 102 | message_type=None, enum_type=None, containing_type=None, 103 | is_extension=False, extension_scope=None, 104 | options=None), 105 | _descriptor.FieldDescriptor( 106 | name='valeur_inconnue11', full_name='WifiDetected.Location.valeur_inconnue11', index=10, 107 | number=11, type=3, cpp_type=2, label=1, 108 | has_default_value=False, default_value=0, 109 | message_type=None, enum_type=None, containing_type=None, 110 | is_extension=False, extension_scope=None, 111 | options=None), 112 | _descriptor.FieldDescriptor( 113 | name='valeur_inconnue12', full_name='WifiDetected.Location.valeur_inconnue12', index=11, 114 | number=12, type=3, cpp_type=2, label=1, 115 | has_default_value=False, default_value=0, 116 | message_type=None, enum_type=None, containing_type=None, 117 | is_extension=False, extension_scope=None, 118 | options=None), 119 | _descriptor.FieldDescriptor( 120 | name='valeur_inconnue21', full_name='WifiDetected.Location.valeur_inconnue21', index=12, 121 | number=21, type=3, cpp_type=2, label=1, 122 | has_default_value=False, default_value=0, 123 | message_type=None, enum_type=None, containing_type=None, 124 | is_extension=False, extension_scope=None, 125 | options=None), 126 | ], 127 | extensions=[ 128 | ], 129 | nested_types=[], 130 | enum_types=[ 131 | ], 132 | options=None, 133 | is_extendable=False, 134 | extension_ranges=[], 135 | oneofs=[ 136 | ], 137 | serialized_start=95, 138 | serialized_end=432, 139 | ) 140 | 141 | _WIFIDETECTED = _descriptor.Descriptor( 142 | name='WifiDetected', 143 | full_name='WifiDetected', 144 | filename=None, 145 | file=DESCRIPTOR, 146 | containing_type=None, 147 | fields=[ 148 | _descriptor.FieldDescriptor( 149 | name='bssid', full_name='WifiDetected.bssid', index=0, 150 | number=1, type=9, cpp_type=9, label=2, 151 | has_default_value=False, default_value=_b("").decode('utf-8'), 152 | message_type=None, enum_type=None, containing_type=None, 153 | is_extension=False, extension_scope=None, 154 | options=None), 155 | _descriptor.FieldDescriptor( 156 | name='location', full_name='WifiDetected.location', index=1, 157 | number=2, type=11, cpp_type=10, label=1, 158 | has_default_value=False, default_value=None, 159 | message_type=None, enum_type=None, containing_type=None, 160 | is_extension=False, extension_scope=None, 161 | options=None), 162 | ], 163 | extensions=[ 164 | ], 165 | nested_types=[_WIFIDETECTED_LOCATION, ], 166 | enum_types=[ 167 | ], 168 | options=None, 169 | is_extendable=False, 170 | extension_ranges=[], 171 | oneofs=[ 172 | ], 173 | serialized_start=21, 174 | serialized_end=432, 175 | ) 176 | 177 | 178 | _BLOCKBSSIDAPPLE = _descriptor.Descriptor( 179 | name='BlockBSSIDApple', 180 | full_name='BlockBSSIDApple', 181 | filename=None, 182 | file=DESCRIPTOR, 183 | containing_type=None, 184 | fields=[ 185 | _descriptor.FieldDescriptor( 186 | name='valeur_inconnue0', full_name='BlockBSSIDApple.valeur_inconnue0', index=0, 187 | number=1, type=3, cpp_type=2, label=1, 188 | has_default_value=False, default_value=0, 189 | message_type=None, enum_type=None, containing_type=None, 190 | is_extension=False, extension_scope=None, 191 | options=None), 192 | _descriptor.FieldDescriptor( 193 | name='wifi', full_name='BlockBSSIDApple.wifi', index=1, 194 | number=2, type=11, cpp_type=10, label=3, 195 | has_default_value=False, default_value=[], 196 | message_type=None, enum_type=None, containing_type=None, 197 | is_extension=False, extension_scope=None, 198 | options=None), 199 | _descriptor.FieldDescriptor( 200 | name='valeur_inconnue1', full_name='BlockBSSIDApple.valeur_inconnue1', index=2, 201 | number=3, type=5, cpp_type=1, label=1, 202 | has_default_value=False, default_value=0, 203 | message_type=None, enum_type=None, containing_type=None, 204 | is_extension=False, extension_scope=None, 205 | options=None), 206 | _descriptor.FieldDescriptor( 207 | name='valeur_inconnue2', full_name='BlockBSSIDApple.valeur_inconnue2', index=3, 208 | number=4, type=5, cpp_type=1, label=1, 209 | has_default_value=False, default_value=0, 210 | message_type=None, enum_type=None, containing_type=None, 211 | is_extension=False, extension_scope=None, 212 | options=None), 213 | _descriptor.FieldDescriptor( 214 | name='APIName', full_name='BlockBSSIDApple.APIName', index=4, 215 | number=5, type=9, cpp_type=9, label=1, 216 | has_default_value=False, default_value=_b("").decode('utf-8'), 217 | message_type=None, enum_type=None, containing_type=None, 218 | is_extension=False, extension_scope=None, 219 | options=None), 220 | ], 221 | extensions=[ 222 | ], 223 | nested_types=[], 224 | enum_types=[ 225 | ], 226 | options=None, 227 | is_extendable=False, 228 | extension_ranges=[], 229 | oneofs=[ 230 | ], 231 | serialized_start=435, 232 | serialized_end=576, 233 | ) 234 | 235 | _WIFIDETECTED_LOCATION.containing_type = _WIFIDETECTED 236 | _WIFIDETECTED.fields_by_name['location'].message_type = _WIFIDETECTED_LOCATION 237 | _BLOCKBSSIDAPPLE.fields_by_name['wifi'].message_type = _WIFIDETECTED 238 | DESCRIPTOR.message_types_by_name['WifiDetected'] = _WIFIDETECTED 239 | DESCRIPTOR.message_types_by_name['BlockBSSIDApple'] = _BLOCKBSSIDAPPLE 240 | 241 | WifiDetected = _reflection.GeneratedProtocolMessageType('WifiDetected', (_message.Message,), dict( 242 | 243 | Location = _reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict( 244 | DESCRIPTOR = _WIFIDETECTED_LOCATION, 245 | __module__ = 'BSSIDApple_pb2' 246 | # @@protoc_insertion_point(class_scope:WifiDetected.Location) 247 | )) 248 | , 249 | DESCRIPTOR = _WIFIDETECTED, 250 | __module__ = 'BSSIDApple_pb2' 251 | # @@protoc_insertion_point(class_scope:WifiDetected) 252 | )) 253 | _sym_db.RegisterMessage(WifiDetected) 254 | _sym_db.RegisterMessage(WifiDetected.Location) 255 | 256 | BlockBSSIDApple = _reflection.GeneratedProtocolMessageType('BlockBSSIDApple', (_message.Message,), dict( 257 | DESCRIPTOR = _BLOCKBSSIDAPPLE, 258 | __module__ = 'BSSIDApple_pb2' 259 | # @@protoc_insertion_point(class_scope:BlockBSSIDApple) 260 | )) 261 | _sym_db.RegisterMessage(BlockBSSIDApple) 262 | 263 | 264 | # @@protoc_insertion_point(module_scope) 265 | -------------------------------------------------------------------------------- /location_utils/GSM.proto: -------------------------------------------------------------------------------- 1 | message MyCell { 2 | optional int64 MCC = 1; 3 | optional int64 MNC = 2; 4 | optional int64 CID = 3; 5 | optional int64 LAC = 4; 6 | } 7 | 8 | message CellReqToApple1 { // seen on iphone4s e.g. 505:1:8784:21696338 result type 1 with channel ID in #11 9 | repeated MyCell cell = 1; 10 | optional int64 param3 = 3; 11 | optional int64 param4 = 4; 12 | optional string ua = 5; 13 | } 14 | 15 | message CellReqToApple25 { // seen on iphone5s e.g. 505:1:12300:135692802 result type 22, no channel number? 16 | required MyCell cell = 25; 17 | optional int64 unknown3 = 3; 18 | optional int64 unknown4 = 4; 19 | } 20 | 21 | message CellResponse1 { // GSM/3G? 22 | optional int64 MCC = 1; 23 | optional int64 MNC = 2; 24 | optional int64 CID = 3; 25 | optional int64 LAC = 4; 26 | message Location { 27 | required int64 latitude = 1; 28 | required int64 longitude = 2; 29 | optional int64 data3 = 3; 30 | optional int64 data4 = 4; 31 | optional int64 data12 = 12; 32 | } 33 | optional Location location = 5; 34 | optional int64 channel = 11; 35 | optional int64 data12 = 12; 36 | } 37 | 38 | message CellResponse22 { // LTE? 39 | optional int64 MCC = 1; 40 | optional int64 MNC = 2; 41 | optional int64 CID = 3; 42 | optional int64 LAC = 4; 43 | message Location { 44 | required int64 latitude = 1; 45 | required int64 longitude = 2; 46 | optional int64 confidence = 3; 47 | optional int64 data4 = 4; 48 | optional int64 data12 = 12; 49 | } 50 | optional Location location = 5; 51 | optional int64 channel = 6; 52 | } 53 | 54 | message CellInfoFromApple22 { // results in CellResponse22 55 | repeated CellResponse22 cell = 22; 56 | } 57 | 58 | message CellInfoFromApple1 { // results in CellResponse1 59 | repeated CellResponse1 cell = 1; 60 | } -------------------------------------------------------------------------------- /location_utils/GSM_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: GSM.proto 3 | 4 | import sys 5 | 6 | _b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode('latin1')) 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='GSM.proto', 18 | package='', 19 | serialized_pb=_b( 20 | '\n\tGSM.proto\"<\n\x06MyCell\x12\x0b\n\x03MCC\x18\x01 \x01(\x03\x12\x0b\n\x03MNC\x18\x02 \x01(\x03\x12\x0b\n\x03\x43ID\x18\x03 \x01(\x03\x12\x0b\n\x03LAC\x18\x04 \x01(\x03\"T\n\x0f\x43\x65llReqToApple1\x12\x15\n\x04\x63\x65ll\x18\x01 \x03(\x0b\x32\x07.MyCell\x12\x0e\n\x06param3\x18\x03 \x01(\x03\x12\x0e\n\x06param4\x18\x04 \x01(\x03\x12\n\n\x02ua\x18\x05 \x01(\t\"M\n\x10\x43\x65llReqToApple25\x12\x15\n\x04\x63\x65ll\x18\x19 \x02(\x0b\x32\x07.MyCell\x12\x10\n\x08unknown3\x18\x03 \x01(\x03\x12\x10\n\x08unknown4\x18\x04 \x01(\x03\"\xee\x01\n\rCellResponse1\x12\x0b\n\x03MCC\x18\x01 \x01(\x03\x12\x0b\n\x03MNC\x18\x02 \x01(\x03\x12\x0b\n\x03\x43ID\x18\x03 \x01(\x03\x12\x0b\n\x03LAC\x18\x04 \x01(\x03\x12)\n\x08location\x18\x05 \x01(\x0b\x32\x17.CellResponse1.Location\x12\x0f\n\x07\x63hannel\x18\x0b \x01(\x03\x12\x0e\n\x06\x64\x61ta12\x18\x0c \x01(\x03\x1a]\n\x08Location\x12\x10\n\x08latitude\x18\x01 \x02(\x03\x12\x11\n\tlongitude\x18\x02 \x02(\x03\x12\r\n\x05\x64\x61ta3\x18\x03 \x01(\x03\x12\r\n\x05\x64\x61ta4\x18\x04 \x01(\x03\x12\x0e\n\x06\x64\x61ta12\x18\x0c \x01(\x03\"\xe5\x01\n\x0e\x43\x65llResponse22\x12\x0b\n\x03MCC\x18\x01 \x01(\x03\x12\x0b\n\x03MNC\x18\x02 \x01(\x03\x12\x0b\n\x03\x43ID\x18\x03 \x01(\x03\x12\x0b\n\x03LAC\x18\x04 \x01(\x03\x12*\n\x08location\x18\x05 \x01(\x0b\x32\x18.CellResponse22.Location\x12\x0f\n\x07\x63hannel\x18\x06 \x01(\x03\x1a\x62\n\x08Location\x12\x10\n\x08latitude\x18\x01 \x02(\x03\x12\x11\n\tlongitude\x18\x02 \x02(\x03\x12\x12\n\nconfidence\x18\x03 \x01(\x03\x12\r\n\x05\x64\x61ta4\x18\x04 \x01(\x03\x12\x0e\n\x06\x64\x61ta12\x18\x0c \x01(\x03\"4\n\x13\x43\x65llInfoFromApple22\x12\x1d\n\x04\x63\x65ll\x18\x16 \x03(\x0b\x32\x0f.CellResponse22\"2\n\x12\x43\x65llInfoFromApple1\x12\x1c\n\x04\x63\x65ll\x18\x01 \x03(\x0b\x32\x0e.CellResponse1') 21 | ) 22 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 23 | 24 | _MYCELL = _descriptor.Descriptor( 25 | name='MyCell', 26 | full_name='MyCell', 27 | filename=None, 28 | file=DESCRIPTOR, 29 | containing_type=None, 30 | fields=[ 31 | _descriptor.FieldDescriptor( 32 | name='MCC', full_name='MyCell.MCC', index=0, 33 | number=1, type=3, cpp_type=2, label=1, 34 | has_default_value=False, default_value=0, 35 | message_type=None, enum_type=None, containing_type=None, 36 | is_extension=False, extension_scope=None, 37 | options=None), 38 | _descriptor.FieldDescriptor( 39 | name='MNC', full_name='MyCell.MNC', index=1, 40 | number=2, type=3, cpp_type=2, label=1, 41 | has_default_value=False, default_value=0, 42 | message_type=None, enum_type=None, containing_type=None, 43 | is_extension=False, extension_scope=None, 44 | options=None), 45 | _descriptor.FieldDescriptor( 46 | name='CID', full_name='MyCell.CID', index=2, 47 | number=3, type=3, cpp_type=2, label=1, 48 | has_default_value=False, default_value=0, 49 | message_type=None, enum_type=None, containing_type=None, 50 | is_extension=False, extension_scope=None, 51 | options=None), 52 | _descriptor.FieldDescriptor( 53 | name='LAC', full_name='MyCell.LAC', index=3, 54 | number=4, type=3, cpp_type=2, label=1, 55 | has_default_value=False, default_value=0, 56 | message_type=None, enum_type=None, containing_type=None, 57 | is_extension=False, extension_scope=None, 58 | options=None), 59 | ], 60 | extensions=[ 61 | ], 62 | nested_types=[], 63 | enum_types=[ 64 | ], 65 | options=None, 66 | is_extendable=False, 67 | extension_ranges=[], 68 | oneofs=[ 69 | ], 70 | serialized_start=13, 71 | serialized_end=73, 72 | ) 73 | 74 | _CELLREQTOAPPLE1 = _descriptor.Descriptor( 75 | name='CellReqToApple1', 76 | full_name='CellReqToApple1', 77 | filename=None, 78 | file=DESCRIPTOR, 79 | containing_type=None, 80 | fields=[ 81 | _descriptor.FieldDescriptor( 82 | name='cell', full_name='CellReqToApple1.cell', index=0, 83 | number=1, type=11, cpp_type=10, label=3, 84 | has_default_value=False, default_value=[], 85 | message_type=None, enum_type=None, containing_type=None, 86 | is_extension=False, extension_scope=None, 87 | options=None), 88 | _descriptor.FieldDescriptor( 89 | name='param3', full_name='CellReqToApple1.param3', index=1, 90 | number=3, type=3, cpp_type=2, label=1, 91 | has_default_value=False, default_value=0, 92 | message_type=None, enum_type=None, containing_type=None, 93 | is_extension=False, extension_scope=None, 94 | options=None), 95 | _descriptor.FieldDescriptor( 96 | name='param4', full_name='CellReqToApple1.param4', index=2, 97 | number=4, type=3, cpp_type=2, label=1, 98 | has_default_value=False, default_value=0, 99 | message_type=None, enum_type=None, containing_type=None, 100 | is_extension=False, extension_scope=None, 101 | options=None), 102 | _descriptor.FieldDescriptor( 103 | name='ua', full_name='CellReqToApple1.ua', index=3, 104 | number=5, type=9, cpp_type=9, label=1, 105 | has_default_value=False, default_value=_b("").decode('utf-8'), 106 | message_type=None, enum_type=None, containing_type=None, 107 | is_extension=False, extension_scope=None, 108 | options=None), 109 | ], 110 | extensions=[ 111 | ], 112 | nested_types=[], 113 | enum_types=[ 114 | ], 115 | options=None, 116 | is_extendable=False, 117 | extension_ranges=[], 118 | oneofs=[ 119 | ], 120 | serialized_start=75, 121 | serialized_end=159, 122 | ) 123 | 124 | _CELLREQTOAPPLE25 = _descriptor.Descriptor( 125 | name='CellReqToApple25', 126 | full_name='CellReqToApple25', 127 | filename=None, 128 | file=DESCRIPTOR, 129 | containing_type=None, 130 | fields=[ 131 | _descriptor.FieldDescriptor( 132 | name='cell', full_name='CellReqToApple25.cell', index=0, 133 | number=25, type=11, cpp_type=10, label=2, 134 | has_default_value=False, default_value=None, 135 | message_type=None, enum_type=None, containing_type=None, 136 | is_extension=False, extension_scope=None, 137 | options=None), 138 | _descriptor.FieldDescriptor( 139 | name='unknown3', full_name='CellReqToApple25.unknown3', index=1, 140 | number=3, type=3, cpp_type=2, label=1, 141 | has_default_value=False, default_value=0, 142 | message_type=None, enum_type=None, containing_type=None, 143 | is_extension=False, extension_scope=None, 144 | options=None), 145 | _descriptor.FieldDescriptor( 146 | name='unknown4', full_name='CellReqToApple25.unknown4', index=2, 147 | number=4, type=3, cpp_type=2, label=1, 148 | has_default_value=False, default_value=0, 149 | message_type=None, enum_type=None, containing_type=None, 150 | is_extension=False, extension_scope=None, 151 | options=None), 152 | ], 153 | extensions=[ 154 | ], 155 | nested_types=[], 156 | enum_types=[ 157 | ], 158 | options=None, 159 | is_extendable=False, 160 | extension_ranges=[], 161 | oneofs=[ 162 | ], 163 | serialized_start=161, 164 | serialized_end=238, 165 | ) 166 | 167 | _CELLRESPONSE1_LOCATION = _descriptor.Descriptor( 168 | name='Location', 169 | full_name='CellResponse1.Location', 170 | filename=None, 171 | file=DESCRIPTOR, 172 | containing_type=None, 173 | fields=[ 174 | _descriptor.FieldDescriptor( 175 | name='latitude', full_name='CellResponse1.Location.latitude', index=0, 176 | number=1, type=3, cpp_type=2, label=2, 177 | has_default_value=False, default_value=0, 178 | message_type=None, enum_type=None, containing_type=None, 179 | is_extension=False, extension_scope=None, 180 | options=None), 181 | _descriptor.FieldDescriptor( 182 | name='longitude', full_name='CellResponse1.Location.longitude', index=1, 183 | number=2, type=3, cpp_type=2, label=2, 184 | has_default_value=False, default_value=0, 185 | message_type=None, enum_type=None, containing_type=None, 186 | is_extension=False, extension_scope=None, 187 | options=None), 188 | _descriptor.FieldDescriptor( 189 | name='data3', full_name='CellResponse1.Location.data3', index=2, 190 | number=3, type=3, cpp_type=2, label=1, 191 | has_default_value=False, default_value=0, 192 | message_type=None, enum_type=None, containing_type=None, 193 | is_extension=False, extension_scope=None, 194 | options=None), 195 | _descriptor.FieldDescriptor( 196 | name='data4', full_name='CellResponse1.Location.data4', index=3, 197 | number=4, type=3, cpp_type=2, label=1, 198 | has_default_value=False, default_value=0, 199 | message_type=None, enum_type=None, containing_type=None, 200 | is_extension=False, extension_scope=None, 201 | options=None), 202 | _descriptor.FieldDescriptor( 203 | name='data12', full_name='CellResponse1.Location.data12', index=4, 204 | number=12, type=3, cpp_type=2, label=1, 205 | has_default_value=False, default_value=0, 206 | message_type=None, enum_type=None, containing_type=None, 207 | is_extension=False, extension_scope=None, 208 | options=None), 209 | ], 210 | extensions=[ 211 | ], 212 | nested_types=[], 213 | enum_types=[ 214 | ], 215 | options=None, 216 | is_extendable=False, 217 | extension_ranges=[], 218 | oneofs=[ 219 | ], 220 | serialized_start=386, 221 | serialized_end=479, 222 | ) 223 | 224 | _CELLRESPONSE1 = _descriptor.Descriptor( 225 | name='CellResponse1', 226 | full_name='CellResponse1', 227 | filename=None, 228 | file=DESCRIPTOR, 229 | containing_type=None, 230 | fields=[ 231 | _descriptor.FieldDescriptor( 232 | name='MCC', full_name='CellResponse1.MCC', index=0, 233 | number=1, type=3, cpp_type=2, label=1, 234 | has_default_value=False, default_value=0, 235 | message_type=None, enum_type=None, containing_type=None, 236 | is_extension=False, extension_scope=None, 237 | options=None), 238 | _descriptor.FieldDescriptor( 239 | name='MNC', full_name='CellResponse1.MNC', index=1, 240 | number=2, type=3, cpp_type=2, label=1, 241 | has_default_value=False, default_value=0, 242 | message_type=None, enum_type=None, containing_type=None, 243 | is_extension=False, extension_scope=None, 244 | options=None), 245 | _descriptor.FieldDescriptor( 246 | name='CID', full_name='CellResponse1.CID', index=2, 247 | number=3, type=3, cpp_type=2, label=1, 248 | has_default_value=False, default_value=0, 249 | message_type=None, enum_type=None, containing_type=None, 250 | is_extension=False, extension_scope=None, 251 | options=None), 252 | _descriptor.FieldDescriptor( 253 | name='LAC', full_name='CellResponse1.LAC', index=3, 254 | number=4, type=3, cpp_type=2, label=1, 255 | has_default_value=False, default_value=0, 256 | message_type=None, enum_type=None, containing_type=None, 257 | is_extension=False, extension_scope=None, 258 | options=None), 259 | _descriptor.FieldDescriptor( 260 | name='location', full_name='CellResponse1.location', index=4, 261 | number=5, type=11, cpp_type=10, label=1, 262 | has_default_value=False, default_value=None, 263 | message_type=None, enum_type=None, containing_type=None, 264 | is_extension=False, extension_scope=None, 265 | options=None), 266 | _descriptor.FieldDescriptor( 267 | name='channel', full_name='CellResponse1.channel', index=5, 268 | number=11, type=3, cpp_type=2, label=1, 269 | has_default_value=False, default_value=0, 270 | message_type=None, enum_type=None, containing_type=None, 271 | is_extension=False, extension_scope=None, 272 | options=None), 273 | _descriptor.FieldDescriptor( 274 | name='data12', full_name='CellResponse1.data12', index=6, 275 | number=12, type=3, cpp_type=2, label=1, 276 | has_default_value=False, default_value=0, 277 | message_type=None, enum_type=None, containing_type=None, 278 | is_extension=False, extension_scope=None, 279 | options=None), 280 | ], 281 | extensions=[ 282 | ], 283 | nested_types=[_CELLRESPONSE1_LOCATION, ], 284 | enum_types=[ 285 | ], 286 | options=None, 287 | is_extendable=False, 288 | extension_ranges=[], 289 | oneofs=[ 290 | ], 291 | serialized_start=241, 292 | serialized_end=479, 293 | ) 294 | 295 | _CELLRESPONSE22_LOCATION = _descriptor.Descriptor( 296 | name='Location', 297 | full_name='CellResponse22.Location', 298 | filename=None, 299 | file=DESCRIPTOR, 300 | containing_type=None, 301 | fields=[ 302 | _descriptor.FieldDescriptor( 303 | name='latitude', full_name='CellResponse22.Location.latitude', index=0, 304 | number=1, type=3, cpp_type=2, label=2, 305 | has_default_value=False, default_value=0, 306 | message_type=None, enum_type=None, containing_type=None, 307 | is_extension=False, extension_scope=None, 308 | options=None), 309 | _descriptor.FieldDescriptor( 310 | name='longitude', full_name='CellResponse22.Location.longitude', index=1, 311 | number=2, type=3, cpp_type=2, label=2, 312 | has_default_value=False, default_value=0, 313 | message_type=None, enum_type=None, containing_type=None, 314 | is_extension=False, extension_scope=None, 315 | options=None), 316 | _descriptor.FieldDescriptor( 317 | name='confidence', full_name='CellResponse22.Location.confidence', index=2, 318 | number=3, type=3, cpp_type=2, label=1, 319 | has_default_value=False, default_value=0, 320 | message_type=None, enum_type=None, containing_type=None, 321 | is_extension=False, extension_scope=None, 322 | options=None), 323 | _descriptor.FieldDescriptor( 324 | name='data4', full_name='CellResponse22.Location.data4', index=3, 325 | number=4, type=3, cpp_type=2, label=1, 326 | has_default_value=False, default_value=0, 327 | message_type=None, enum_type=None, containing_type=None, 328 | is_extension=False, extension_scope=None, 329 | options=None), 330 | _descriptor.FieldDescriptor( 331 | name='data12', full_name='CellResponse22.Location.data12', index=4, 332 | number=12, type=3, cpp_type=2, label=1, 333 | has_default_value=False, default_value=0, 334 | message_type=None, enum_type=None, containing_type=None, 335 | is_extension=False, extension_scope=None, 336 | options=None), 337 | ], 338 | extensions=[ 339 | ], 340 | nested_types=[], 341 | enum_types=[ 342 | ], 343 | options=None, 344 | is_extendable=False, 345 | extension_ranges=[], 346 | oneofs=[ 347 | ], 348 | serialized_start=613, 349 | serialized_end=711, 350 | ) 351 | 352 | _CELLRESPONSE22 = _descriptor.Descriptor( 353 | name='CellResponse22', 354 | full_name='CellResponse22', 355 | filename=None, 356 | file=DESCRIPTOR, 357 | containing_type=None, 358 | fields=[ 359 | _descriptor.FieldDescriptor( 360 | name='MCC', full_name='CellResponse22.MCC', index=0, 361 | number=1, type=3, cpp_type=2, label=1, 362 | has_default_value=False, default_value=0, 363 | message_type=None, enum_type=None, containing_type=None, 364 | is_extension=False, extension_scope=None, 365 | options=None), 366 | _descriptor.FieldDescriptor( 367 | name='MNC', full_name='CellResponse22.MNC', index=1, 368 | number=2, type=3, cpp_type=2, label=1, 369 | has_default_value=False, default_value=0, 370 | message_type=None, enum_type=None, containing_type=None, 371 | is_extension=False, extension_scope=None, 372 | options=None), 373 | _descriptor.FieldDescriptor( 374 | name='CID', full_name='CellResponse22.CID', index=2, 375 | number=3, type=3, cpp_type=2, label=1, 376 | has_default_value=False, default_value=0, 377 | message_type=None, enum_type=None, containing_type=None, 378 | is_extension=False, extension_scope=None, 379 | options=None), 380 | _descriptor.FieldDescriptor( 381 | name='LAC', full_name='CellResponse22.LAC', index=3, 382 | number=4, type=3, cpp_type=2, label=1, 383 | has_default_value=False, default_value=0, 384 | message_type=None, enum_type=None, containing_type=None, 385 | is_extension=False, extension_scope=None, 386 | options=None), 387 | _descriptor.FieldDescriptor( 388 | name='location', full_name='CellResponse22.location', index=4, 389 | number=5, type=11, cpp_type=10, label=1, 390 | has_default_value=False, default_value=None, 391 | message_type=None, enum_type=None, containing_type=None, 392 | is_extension=False, extension_scope=None, 393 | options=None), 394 | _descriptor.FieldDescriptor( 395 | name='channel', full_name='CellResponse22.channel', index=5, 396 | number=6, type=3, cpp_type=2, label=1, 397 | has_default_value=False, default_value=0, 398 | message_type=None, enum_type=None, containing_type=None, 399 | is_extension=False, extension_scope=None, 400 | options=None), 401 | ], 402 | extensions=[ 403 | ], 404 | nested_types=[_CELLRESPONSE22_LOCATION, ], 405 | enum_types=[ 406 | ], 407 | options=None, 408 | is_extendable=False, 409 | extension_ranges=[], 410 | oneofs=[ 411 | ], 412 | serialized_start=482, 413 | serialized_end=711, 414 | ) 415 | 416 | _CELLINFOFROMAPPLE22 = _descriptor.Descriptor( 417 | name='CellInfoFromApple22', 418 | full_name='CellInfoFromApple22', 419 | filename=None, 420 | file=DESCRIPTOR, 421 | containing_type=None, 422 | fields=[ 423 | _descriptor.FieldDescriptor( 424 | name='cell', full_name='CellInfoFromApple22.cell', index=0, 425 | number=22, type=11, cpp_type=10, label=3, 426 | has_default_value=False, default_value=[], 427 | message_type=None, enum_type=None, containing_type=None, 428 | is_extension=False, extension_scope=None, 429 | options=None), 430 | ], 431 | extensions=[ 432 | ], 433 | nested_types=[], 434 | enum_types=[ 435 | ], 436 | options=None, 437 | is_extendable=False, 438 | extension_ranges=[], 439 | oneofs=[ 440 | ], 441 | serialized_start=713, 442 | serialized_end=765, 443 | ) 444 | 445 | _CELLINFOFROMAPPLE1 = _descriptor.Descriptor( 446 | name='CellInfoFromApple1', 447 | full_name='CellInfoFromApple1', 448 | filename=None, 449 | file=DESCRIPTOR, 450 | containing_type=None, 451 | fields=[ 452 | _descriptor.FieldDescriptor( 453 | name='cell', full_name='CellInfoFromApple1.cell', index=0, 454 | number=1, type=11, cpp_type=10, label=3, 455 | has_default_value=False, default_value=[], 456 | message_type=None, enum_type=None, containing_type=None, 457 | is_extension=False, extension_scope=None, 458 | options=None), 459 | ], 460 | extensions=[ 461 | ], 462 | nested_types=[], 463 | enum_types=[ 464 | ], 465 | options=None, 466 | is_extendable=False, 467 | extension_ranges=[], 468 | oneofs=[ 469 | ], 470 | serialized_start=767, 471 | serialized_end=817, 472 | ) 473 | 474 | _CELLREQTOAPPLE1.fields_by_name['cell'].message_type = _MYCELL 475 | _CELLREQTOAPPLE25.fields_by_name['cell'].message_type = _MYCELL 476 | _CELLRESPONSE1_LOCATION.containing_type = _CELLRESPONSE1 477 | _CELLRESPONSE1.fields_by_name['location'].message_type = _CELLRESPONSE1_LOCATION 478 | _CELLRESPONSE22_LOCATION.containing_type = _CELLRESPONSE22 479 | _CELLRESPONSE22.fields_by_name['location'].message_type = _CELLRESPONSE22_LOCATION 480 | _CELLINFOFROMAPPLE22.fields_by_name['cell'].message_type = _CELLRESPONSE22 481 | _CELLINFOFROMAPPLE1.fields_by_name['cell'].message_type = _CELLRESPONSE1 482 | DESCRIPTOR.message_types_by_name['MyCell'] = _MYCELL 483 | DESCRIPTOR.message_types_by_name['CellReqToApple1'] = _CELLREQTOAPPLE1 484 | DESCRIPTOR.message_types_by_name['CellReqToApple25'] = _CELLREQTOAPPLE25 485 | DESCRIPTOR.message_types_by_name['CellResponse1'] = _CELLRESPONSE1 486 | DESCRIPTOR.message_types_by_name['CellResponse22'] = _CELLRESPONSE22 487 | DESCRIPTOR.message_types_by_name['CellInfoFromApple22'] = _CELLINFOFROMAPPLE22 488 | DESCRIPTOR.message_types_by_name['CellInfoFromApple1'] = _CELLINFOFROMAPPLE1 489 | 490 | MyCell = _reflection.GeneratedProtocolMessageType('MyCell', (_message.Message,), dict( 491 | DESCRIPTOR=_MYCELL, 492 | __module__='GSM_pb2' 493 | # @@protoc_insertion_point(class_scope:MyCell) 494 | )) 495 | _sym_db.RegisterMessage(MyCell) 496 | 497 | CellReqToApple1 = _reflection.GeneratedProtocolMessageType('CellReqToApple1', (_message.Message,), dict( 498 | DESCRIPTOR=_CELLREQTOAPPLE1, 499 | __module__='GSM_pb2' 500 | # @@protoc_insertion_point(class_scope:CellReqToApple1) 501 | )) 502 | _sym_db.RegisterMessage(CellReqToApple1) 503 | 504 | CellReqToApple25 = _reflection.GeneratedProtocolMessageType('CellReqToApple25', (_message.Message,), dict( 505 | DESCRIPTOR=_CELLREQTOAPPLE25, 506 | __module__='GSM_pb2' 507 | # @@protoc_insertion_point(class_scope:CellReqToApple25) 508 | )) 509 | _sym_db.RegisterMessage(CellReqToApple25) 510 | 511 | CellResponse1 = _reflection.GeneratedProtocolMessageType('CellResponse1', (_message.Message,), dict( 512 | 513 | Location=_reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict( 514 | DESCRIPTOR=_CELLRESPONSE1_LOCATION, 515 | __module__='GSM_pb2' 516 | # @@protoc_insertion_point(class_scope:CellResponse1.Location) 517 | )) 518 | , 519 | DESCRIPTOR=_CELLRESPONSE1, 520 | __module__='GSM_pb2' 521 | # @@protoc_insertion_point(class_scope:CellResponse1) 522 | )) 523 | _sym_db.RegisterMessage(CellResponse1) 524 | _sym_db.RegisterMessage(CellResponse1.Location) 525 | 526 | CellResponse22 = _reflection.GeneratedProtocolMessageType('CellResponse22', (_message.Message,), dict( 527 | 528 | Location=_reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict( 529 | DESCRIPTOR=_CELLRESPONSE22_LOCATION, 530 | __module__='GSM_pb2' 531 | # @@protoc_insertion_point(class_scope:CellResponse22.Location) 532 | )) 533 | , 534 | DESCRIPTOR=_CELLRESPONSE22, 535 | __module__='GSM_pb2' 536 | # @@protoc_insertion_point(class_scope:CellResponse22) 537 | )) 538 | _sym_db.RegisterMessage(CellResponse22) 539 | _sym_db.RegisterMessage(CellResponse22.Location) 540 | 541 | CellInfoFromApple22 = _reflection.GeneratedProtocolMessageType('CellInfoFromApple22', (_message.Message,), dict( 542 | DESCRIPTOR=_CELLINFOFROMAPPLE22, 543 | __module__='GSM_pb2' 544 | # @@protoc_insertion_point(class_scope:CellInfoFromApple22) 545 | )) 546 | _sym_db.RegisterMessage(CellInfoFromApple22) 547 | 548 | CellInfoFromApple1 = _reflection.GeneratedProtocolMessageType('CellInfoFromApple1', (_message.Message,), dict( 549 | DESCRIPTOR=_CELLINFOFROMAPPLE1, 550 | __module__='GSM_pb2' 551 | # @@protoc_insertion_point(class_scope:CellInfoFromApple1) 552 | )) 553 | _sym_db.RegisterMessage(CellInfoFromApple1) 554 | 555 | 556 | # @@protoc_insertion_point(module_scope) 557 | -------------------------------------------------------------------------------- /location_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/location_utils/__init__.py -------------------------------------------------------------------------------- /location_utils/wigle_lib.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | WIGLE_ENDPOINT_API_V2 = 'https://api.wigle.net/api/v2/network/search' 4 | 5 | 6 | class WigleSearch(): 7 | def __init__(self, user, password): 8 | self.user = user 9 | self.password = password 10 | 11 | def search(self, lat_range=None, long_range=None, variance=None, 12 | bssid=None, ssid=None, ssidlike=None, 13 | last_update=None, 14 | address=None, state=None, zipcode=None, 15 | on_new_page=None, max_results=100): 16 | """ 17 | Credit to: https://github.com/viraptor/wigle/blob/master/wigle/__init__.py 18 | Search the Wigle wifi database for matching entries. The following 19 | criteria are supported: 20 | Args: 21 | lat_range ((float, float)): latitude range 22 | long_range ((float, float)): longitude range 23 | variance (float): radius tolerance in degrees 24 | bssid (str): BSSID/MAC of AP 25 | ssid (str): SSID of network 26 | last_update (datetime): when was the AP last seen 27 | address (str): location, address 28 | state (str): location, state 29 | zipcode (str): location, zip code 30 | on_new_page (func(int)): callback to notify when requesting new 31 | page of results 32 | max_results (int): maximum number of results from search query 33 | Returns: 34 | [dict]: list of dicts describing matching wifis 35 | """ 36 | 37 | # onlymine=false&freenet=false&paynet=false&ssidlike=starbucks 38 | 39 | params = { 40 | 'latrange1': lat_range[0] if lat_range else "", 41 | 'latrange2': lat_range[1] if lat_range else "", 42 | 'longrange1': long_range[0] if long_range else "", 43 | 'longrange2': long_range[1] if long_range else "", 44 | 'variance': str(variance) if variance else "0.01", 45 | 'netid': bssid or "", 46 | 'ssid': ssid or "", 47 | 'ssidlike': ssidlike or "", 48 | # Filter points by how recently they've been updated, condensed date/time numeric string format 'yyyyMMddhhmmss' 49 | 'lastupdt': last_update.strftime("%Y%m%d%H%M%S") if last_update else "", 50 | 'onlymine': 'false', 51 | 'freenet': 'false', 52 | 'paynet': 'false', 53 | 'addresscode': address or "", 54 | 'statecode': state or "", 55 | 'zipcode': zipcode or "", 56 | } 57 | 58 | result_wifi = [] 59 | 60 | while True: 61 | if on_new_page: 62 | on_new_page(params.get('first', 1)) 63 | if self.user == '' or self.password == '': 64 | print("Username and/or Password missing for Wigle API") 65 | return [] 66 | 67 | resp = requests.get(WIGLE_ENDPOINT_API_V2, 68 | auth=(self.user, self.password), 69 | params=params) 70 | 71 | print('Wigle API Raw Response: {}'.format(resp.content)) 72 | 73 | data = resp.json() 74 | if not data['success']: 75 | raise_wigle_error(data) 76 | 77 | for result in data['results'][:max_results - len(result_wifi)]: 78 | fix_latlong_nums(result) 79 | result_wifi.append(result) 80 | 81 | if data['resultCount'] < 100 or len(result_wifi) >= max_results: 82 | break 83 | 84 | params['first'] = data['last'] + 1 85 | 86 | return result_wifi 87 | 88 | 89 | def fix_latlong_nums(net): 90 | net['trilat'] = float(net['trilat']) 91 | net['trilong'] = float(net['trilong']) 92 | 93 | 94 | def raise_wigle_error(data): 95 | # {u'error': u'too many queries today', u'success': False} 96 | message = data.get('error') 97 | if message == "too many queries today": 98 | raise WigleRatelimitExceeded() 99 | else: 100 | raise WigleRequestFailure(message) 101 | 102 | 103 | class WigleError(Exception): 104 | pass 105 | 106 | 107 | class WigleRequestFailure(WigleError): 108 | pass 109 | 110 | 111 | class WigleRatelimitExceeded(WigleRequestFailure): 112 | pass 113 | -------------------------------------------------------------------------------- /location_utils/wigle_query.py: -------------------------------------------------------------------------------- 1 | import security_ssid.settings 2 | from location_utils import wigle_lib 3 | 4 | wigle_apiname = security_ssid.settings.wigle_username 5 | wigle_apitoken = security_ssid.settings.wigle_password 6 | 7 | 8 | def get_location(SSID=''): 9 | wigle_search_client = wigle_lib.WigleSearch(wigle_apiname, wigle_apitoken) 10 | wigle_results = wigle_search_client.search(ssid=SSID) 11 | 12 | access_point_results = {} 13 | count_matches = 1 14 | 15 | for result in wigle_results: 16 | lat = float(result['trilat']) 17 | lon = float(result['trilong']) 18 | ssid_result = result['ssid'] 19 | bssid_result = result['netid'] 20 | 21 | # Exact case-sensitive match 22 | if SSID and ssid_result == SSID: 23 | id = '%s [%s] [%s]' % (SSID, bssid_result, count_matches) 24 | access_point_results[id] = (lat, lon) 25 | count_matches += 1 26 | 27 | return access_point_results -------------------------------------------------------------------------------- /location_utils/wloc.py: -------------------------------------------------------------------------------- 1 | # Mostly taken from paper by François-Xavier Aguessy and Côme Demoustier 2 | # http://fxaguessy.fr/rapport-pfe-interception-ssl-analyse-donnees-localisation-smartphones/ 3 | # Updates in 2020 based on this article: https://appelsiini.net/2017/reverse-engineering-location-services/ 4 | 5 | import requests 6 | 7 | from location_utils import BSSIDApple_pb2 8 | from location_utils import GSM_pb2 9 | 10 | 11 | def padBSSID(bssid): 12 | result = '' 13 | for e in bssid.split(':'): 14 | if len(e) == 1: 15 | e = '0%s' % e 16 | result += e + ':' 17 | return result.strip(':') 18 | 19 | 20 | def ListWifiGrannySmith(wifi_list): 21 | access_points_from_wloc = {} 22 | for wifi in wifi_list.wifi: 23 | if wifi.HasField('location'): 24 | lat = wifi.location.latitude * pow(10, -8) 25 | lon = wifi.location.longitude * pow(10, -8) 26 | mac = padBSSID(wifi.bssid) 27 | access_points_from_wloc[mac] = (lat, lon) 28 | 29 | return access_points_from_wloc 30 | 31 | 32 | def ProcessMobileResponse(cell_list): 33 | operators = {1: 'Telstra', 34 | 2: 'Optus', 35 | 3: 'Vodafone', 36 | 6: 'Three'} 37 | celldict = {} 38 | celldesc = {} 39 | 40 | # kml = simplekml.Kml() 41 | for cell in cell_list.cell: 42 | if cell.HasField('location') and cell.CID != -1: # exclude "LAC" type results (usually 20 in each response) 43 | lat = cell.location.latitude * pow(10, -8) 44 | lon = cell.location.longitude * pow(10, -8) 45 | cellid = '%s:%s:%s:%s' % (cell.MCC, cell.MNC, cell.LAC, cell.CID) 46 | # kml.newpoint(name=cellid, coords=[(lon,lat)]) 47 | try: 48 | # cellname = '%s LAC:%s CID:%s [%s %s %s] [%s %s]' % (operators[cell.MNC],cell.LAC,cell.CID,\ 49 | # cell.location.data3,cell.location.data4,cell.location.data12,\ 50 | # cell.data6,cell.data7) 51 | cellname = '%s LAC:%s CID:%s' % (operators[cell.MNC], cell.LAC, cell.CID) 52 | except: 53 | cellname = 'MNC:%s LAC:%s CID:%s' % (cell.MNC, cell.LAC, cell.CID) 54 | try: 55 | if cell.HasField('channel'): 56 | cellname += ' Channel:%s' % cell.channel 57 | except ValueError: 58 | pass 59 | celldict[cellid] = (lat, lon) 60 | celldesc[cellid] = cellname 61 | else: 62 | pass 63 | # print 'Weird cell: %s' % cell 64 | # kml.save("test.kml") 65 | # f=file('result.txt','w') 66 | # for (cid,desc) in celldesc.items(): 67 | # print cid, desc 68 | # f.write('%s %s\n'%(cid,desc)) 69 | # f.close() 70 | # print 'Wrote result.txt' 71 | return (celldict, celldesc) 72 | 73 | 74 | def QueryBSSID(bssid_list): 75 | bssid_wifi_list_pbuf = BSSIDApple_pb2.BlockBSSIDApple() 76 | 77 | if type(bssid_list) == str: 78 | bssid_list = [bssid_list] 79 | elif type(bssid_list) == list: 80 | bssid_list = bssid_list 81 | else: 82 | raise TypeError('Provide 1 BSSID as string or multiple BSSIDs as list of strings') 83 | 84 | for bssid in bssid_list: 85 | wifi = bssid_wifi_list_pbuf.wifi.add() 86 | wifi.bssid = bssid 87 | 88 | wifi_list_string = bssid_wifi_list_pbuf.SerializeToString() 89 | wifi_list_string_length = len(wifi_list_string) 90 | 91 | wloc_headers = {'Content-Type': 'application/x-www-form-urlencoded', 92 | 'Accept': '*/*', 93 | "Accept-Charset": "utf-8", 94 | "Accept-Encoding": "gzip, deflate", 95 | "Accept-Language": "en-us", 96 | 'User-Agent': 'locationd (6.9) CFNetwork/548.1.4 Darwin/14.0.0'} 97 | 98 | binary_header = "\x00\x01\x00\x05" + \ 99 | "en_US" + \ 100 | "\x00\x00\x00\x09" + \ 101 | "5.1.9B177" + \ 102 | "\x00\x00\x00\x01\x00\x00\x00" 103 | 104 | data_bytes_wloc = binary_header.encode() + \ 105 | chr(wifi_list_string_length).encode() + \ 106 | wifi_list_string 107 | 108 | # Format of request: [header][size][message] in 'data' 109 | # CN of cert on this hostname is sometimes *.ls.apple.com / ls.apple.com, so have to disable SSL verify 110 | wloc_req = requests.post('https://gs-loc.apple.com/clls/wloc', 111 | headers=wloc_headers, 112 | data=data_bytes_wloc, 113 | verify=False) 114 | 115 | bssid_wifi_list_pbuf = BSSIDApple_pb2.BlockBSSIDApple() 116 | bssid_wifi_list_pbuf.ParseFromString(wloc_req.content[10:]) 117 | 118 | return ListWifiGrannySmith(bssid_wifi_list_pbuf) 119 | 120 | 121 | def QueryMobile(cellid, LTE=False): 122 | (MCC, MNC, LAC, CID) = map(int, cellid.split(':')) 123 | if LTE: 124 | req = GSM_pb2.CellReqToApple25() # Request type 25 -> Response type 22 (LTE?) 125 | req.cell.MCC = MCC 126 | req.cell.MNC = MNC 127 | req.cell.LAC = LAC 128 | req.cell.CID = CID 129 | else: 130 | req = GSM_pb2.CellReqToApple1() # Request 1 -> Response type 1 (GSM/3G?) 131 | cell = req.cell.add() 132 | cell.MCC = MCC 133 | cell.MNC = MNC 134 | cell.LAC = LAC 135 | cell.CID = CID 136 | # cell2 = req.cell.add() #505:2:33300:151564484 137 | # cell2.MCC = 505 138 | # cell2.MNC = 3 139 | # cell2.LAC = 334 140 | # cell2.CID = 87401254 141 | req.param3 = 0 # this affects whether you get cells or LAC areas 142 | req.param4 = 1 # 143 | req.ua = 'com.apple.Maps' 144 | 145 | req_string = req.SerializeToString() 146 | headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': '*/*', "Accept-Charset": "utf-8", 147 | "Accept-Encoding": "gzip, deflate", \ 148 | "Accept-Language": "en-us", 'User-Agent': 'locationd/1753.17 CFNetwork/711.1.12 Darwin/14.0.0'} 149 | data = "\x00\x01\x00\x05" + "en_US" + "\x00\x13" + "com.apple.locationd" + "\x00\x0c" + "7.0.3.11B511" + "\x00\x00\x00\x01\x00\x00\x00" + chr( 150 | len(req_string)) + req_string; 151 | # data = "\x00\x01\x00\x05"+"en_US"+"\x00\x13"+"com.apple.locationd"+"\x00\x0c"+"6.1.1.10B145"+"\x00\x00\x00\x01\x00\x00\x00"+chr(len(req_string)) + req_string; 152 | # f=file('request.bin','wb') 153 | # f.write(req_string) 154 | # print('Wrote request.bin') 155 | # f.close() 156 | cellid = '%s:%s:%s:%s' % (MCC, MNC, LAC, CID) 157 | print('Querying %s' % cellid) 158 | r = requests.post('https://gs-loc.apple.com/clls/wloc', headers=headers, data=data, 159 | verify=False) # the remote SSL cert CN on this server doesn't match hostname anymore 160 | if LTE: 161 | response = GSM_pb2.CellInfoFromApple22() 162 | else: 163 | response = GSM_pb2.CellInfoFromApple1() 164 | response.ParseFromString(r.content[1:]) 165 | # f=file(cellid+'.bin','wb') 166 | # f.write(r.content[1:]) 167 | # f.close() 168 | # print 'Wrote %s' % (cellid+'.bin') 169 | 170 | return ProcessMobileResponse(response) 171 | 172 | # res = QueryBSSID('b4:5d:50:8f:27:c1') -------------------------------------------------------------------------------- /mac_parser/README.md: -------------------------------------------------------------------------------- 1 | Everything in this /mac_parser subfolder is from this GitHub Repo: 2 | https://github.com/coolbho3k/manuf 3 | 4 | Thanks to Michael Huang for sharing his code on parsing the Wireshark DB. 5 | 6 | Original readme: 7 | 8 | manuf.py 9 | === 10 | 11 | [![Build Status](https://travis-ci.org/coolbho3k/manuf.svg?branch=master)](https://travis-ci.org/coolbho3k/manuf) 12 | 13 | Parser library for Wireshark's OUI database. 14 | --- 15 | 16 | Converts MAC addresses into a manufacturer using Wireshark's OUI database. 17 | 18 | Optimized for quick lookup performance by reading the entire file into memory 19 | on initialization. Maps ranges of MAC addresses to manufacturers and comments 20 | (descriptions). Contains full support for netmasks and other strange things in 21 | the database. 22 | 23 | See [Wireshark's OUI lookup tool](https://www.wireshark.org/tools/oui-lookup.html). 24 | 25 | Written by Michael Huang (coolbho3k). 26 | 27 | Usage 28 | --- 29 | 30 | As a library: 31 | 32 | >>> import manuf 33 | >>> p = manuf.MacParser() 34 | >>> p.get_all('BC:EE:7B:00:00:00') 35 | Vendor(manuf='AsustekC', comment='ASUSTek COMPUTER INC.') 36 | >>> p.get_manuf('BC:EE:7B:00:00:00') 37 | 'AsustekC' 38 | >>> p.get_comment('BC:EE:7B:00:00:00') 39 | 'ASUSTek COMPUTER INC.' 40 | 41 | On the command line. Make sure manuf is in the same directory: 42 | 43 | $ python manuf.py BC:EE:7B:00:00:00 44 | Vendor(manuf='AsustekC', comment='ASUSTek COMPUTER INC.') 45 | 46 | Use a manuf file in a custom location: 47 | 48 | $ python manuf.py --manuf ~/manuf BC:EE:7B:00:00:00 49 | Vendor(manuf='AsustekC', comment='ASUSTek COMPUTER INC.') 50 | 51 | Automatically update the manuf file from Wireshark's git: 52 | 53 | $ python manuf.py --update --manuf ~/manuf BC:EE:7B:00:00:00 54 | Vendor(manuf='AsustekC', comment='ASUSTek COMPUTER INC.') 55 | 56 | Advantages 57 | --- 58 | 59 | Note: the examples use the manuf file provided in the first commit, 9a180b5. 60 | 61 | manuf.py is more accurate than more naive scripts that parse the manuf file. 62 | Critically, it contains support for netmasks. 63 | 64 | For a usual entry, such as BC:EE:7B (AsustekC), the manufacturer "owns" the 65 | last half (24 bits) of the MAC address and is free to assign the addresses 66 | BC:EE:7B:00:00:00 through BC:EE:7B:FF:FF:FF, inclusive, to its devices. 67 | 68 | However, entries like the following also appear commonly in the file: 69 | 70 | 00:1B:C5:00:00:00/36 Convergi # Converging Systems Inc. 71 | 00:1B:C5:00:10:00/36 OpenrbCo # OpenRB.com, Direct SIA 72 | 73 | /36 is a netmask, which means that the listed manufacturer "owns" only the last 74 | 12 bits of the MAC address instead of the usual 24 bits (since MAC addresses 75 | are 48 bits long, and 48 bits - 36 bits = 12 bits). 76 | 77 | This means that Converging Systems is only free to assign the addresss block 78 | 00:1B:C5:00:00:00 through 00:1B:C5:00:0F:FF. Anything after that belongs to 79 | other manufacturers. manuf.py takes this fact into account: 80 | 81 | >>> p.get_manuf('00:1B:C5:00:00:00') 82 | 'Convergi' 83 | >>> p.get_manuf('00:1B:C5:00:0F:FF') 84 | 'Convergi' 85 | >>> p.get_manuf('00:1B:C5:00:10:00') 86 | 'OpenrbCo' 87 | 88 | Even Wireshark's web lookup tool fails here. "00:1B:C5:00:0F:FF" returns only 89 | "IEEE REGISTRATION AUTHORITY" while it should instead return "Converging 90 | Systems Inc." If a netmask is not explicitly specified, a netmask of /24 is 91 | implied. Since this covers most of the entries, most tools only parse the first 92 | 24 bits. 93 | 94 | manuf.py fully supports even more esoteric entries in the database. For example, 95 | consider these two entries: 96 | 97 | 01-80-C2-00-00-30/45 OAM-Multicast-DA-Class-1 98 | 01-80-C2-00-00-38/45 OAM-Multicast-DA-Class-2 99 | 100 | With a netmask of /45, only the last 3 bits of the address are significant. 101 | This means that a device is considered "OAM-Multicast-DA-Class-1" only if the 102 | last digit falls between 0x0 and 0x7 and "OAM-Multicast-DA-Class-2" only if the 103 | last digit falls between 0x8 and 0xF. 104 | 105 | If the last octet is 0x40 or over, or 0x2F or under, the address doesn't belong 106 | to any manufacturer. 107 | 108 | >>> p.get_manuf('01:80:C2:00:00:2F') 109 | >>> p.get_manuf('01:80:C2:00:00:30') 110 | 'OAM-Multicast-DA-Class-1' 111 | >>> p.get_manuf('01:80:C2:00:00:37') 112 | 'OAM-Multicast-DA-Class-1' 113 | >>> p.get_manuf('01:80:C2:00:00:38') 114 | 'OAM-Multicast-DA-Class-2' 115 | >>> p.get_manuf('01:80:C2:00:00:3F') 116 | 'OAM-Multicast-DA-Class-2' 117 | >>> p.get_manuf('01:80:C2:00:00:40') 118 | 119 | Again, the official lookup tool fails here as well, with "01:80:C2:00:00:31" 120 | returning no results. 121 | 122 | Algorithm 123 | --- 124 | 125 | Although optimized for correctness, manuf.py is also quite fast, with average 126 | O(1) lookup time, O(n) setup time, and O(n) memory footprint. 127 | 128 | First, the entire manuf file is read into memory. Each manuf line is stored in 129 | a dict mapping a tuple calculated from the MAC address and netmask to each 130 | manuf: 131 | 132 | ((48 - netmask), macaddress >> (48 - netmask)) 133 | 134 | The (48 - netmask) value is called the "bits left" value in the code. 135 | 136 | For example, Converging Systems' MAC is 0x001BC5000000 and its netmask is 36, 137 | so its key in the dict is this: 138 | 139 | (12, 0x001BC5000000 >> 12) 140 | 141 | To lookup "00:1B:C5:00:0F:FF" we will check the dict beginning with a "bits 142 | left" value of 0, incrementing until we find a match or go over 47 (which means 143 | we have no match): 144 | 145 | (0, 0x001BC5000FFF >> 0) 146 | (1, 0x001BC5000FFF >> 1) 147 | (2, 0x001BC5000FFF >> 2) 148 | ... 149 | (12, 0x001BC5000FFF >> 12) 150 | 151 | Since (12, 0x001BC5000FFF >> 12) equals (12, 0x001BC5000000 >> 12), we have a 152 | match on the 13th iteration of the loop. 153 | 154 | Copying 155 | --- 156 | 157 | This library does not link to Wireshark's manuf database; it merely parses it, 158 | so I have chosen to publish it under the LGPLv3 and Apache License 2.0 159 | instead of the GPLv2. The manuf database is provided for your convenience in 160 | this repository, but will not be updated. 161 | 162 | * License for Python library: LGPLv3 and Apache License 2.0 (dual licensed) 163 | * License for manuf database: GPLv2 164 | 165 | The latest version of the manuf database can be found in the 166 | [Wireshark git repository](https://code.wireshark.org/review/gitweb?p=wireshark.git;a=blob_plain;f=manuf). 167 | The database there is updated about once a week, so you may want to grab the 168 | latest version to use instead of using the one provided here by using the 169 | --update flag on the command line: 170 | 171 | python manuf.py --update 172 | -------------------------------------------------------------------------------- /mac_parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/mac_parser/__init__.py -------------------------------------------------------------------------------- /mac_parser/manuf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # manuf.py: Parser library for Wireshark's OUI database. 4 | # Copyright (c) 2016 Michael Huang 5 | # 6 | # This library is free software. It is dual licensed under the terms of the GNU Lesser General 7 | # Public License version 3.0 (or any later version) and the Apache License version 2.0. 8 | # 9 | # For more information, see: 10 | # 11 | # 12 | # 13 | """Parser library for Wireshark's OUI database. 14 | 15 | Converts MAC addresses into a manufacturer using Wireshark's OUI database. 16 | 17 | See README.md. 18 | 19 | """ 20 | from __future__ import print_function 21 | from collections import namedtuple 22 | import argparse 23 | import re 24 | import sys 25 | import io 26 | import os 27 | 28 | try: 29 | from urllib2 import urlopen 30 | from urllib2 import URLError 31 | except ImportError: 32 | from urllib.request import urlopen 33 | from urllib.error import URLError 34 | 35 | try: 36 | from StringIO import StringIO 37 | except ImportError: 38 | from io import StringIO 39 | 40 | # Vendor tuple 41 | Vendor = namedtuple('Vendor', ['manuf', 'comment']) 42 | 43 | class MacParser(object): 44 | """Class that contains a parser for Wireshark's OUI database. 45 | 46 | Optimized for quick lookup performance by reading the entire file into memory on 47 | initialization. Maps ranges of MAC addresses to manufacturers and comments (descriptions). 48 | Contains full support for netmasks and other strange things in the database. 49 | 50 | See https://www.wireshark.org/tools/oui-lookup.html 51 | 52 | Args: 53 | manuf_name (str): Location of the manuf database file. Defaults to "manuf" in the same 54 | directory. 55 | update (bool): Whether to update the manuf file automatically. Defaults to False. 56 | 57 | Raises: 58 | IOError: If manuf file could not be found. 59 | 60 | """ 61 | MANUF_URL = "https://code.wireshark.org/review/gitweb?p=wireshark.git;a=blob_plain;f=manuf" 62 | 63 | def __init__(self, manuf_name="manuf", update=False): 64 | self._manuf_name = manuf_name 65 | if update: 66 | self.update() 67 | else: 68 | self.refresh() 69 | 70 | def refresh(self, manuf_name=None): 71 | """Refresh/reload manuf database. Call this when manuf file is updated. 72 | 73 | Args: 74 | manuf_name (str): Location of the manuf data base file. Defaults to "manuf" in the 75 | same directory. 76 | 77 | Raises: 78 | IOError: If manuf file could not be found. 79 | 80 | """ 81 | if not manuf_name: 82 | manuf_name = self._manuf_name 83 | with io.open(os.path.join(os.getcwd(), 'mac_parser', manuf_name), "r", encoding="utf-8") as read_file: 84 | manuf_file = StringIO(read_file.read()) 85 | self._masks = {} 86 | 87 | # Build mask -> result dict 88 | for line in manuf_file: 89 | com = line.split("#", 1) 90 | arr = com[0].split() 91 | 92 | if len(arr) < 1: 93 | continue 94 | 95 | parts = arr[0].split("/") 96 | mac_str = self._strip_mac(parts[0]) 97 | mac_int = self._get_mac_int(mac_str) 98 | mask = self._bits_left(mac_str) 99 | 100 | # Specification includes mask 101 | if len(parts) > 1: 102 | mask_spec = 48 - int(parts[1]) 103 | if mask_spec > mask: 104 | mask = mask_spec 105 | 106 | if len(com) > 1: 107 | result = Vendor(manuf=arr[1], comment=com[1].strip()) 108 | else: 109 | result = Vendor(manuf=arr[1], comment=None) 110 | 111 | self._masks[(mask, mac_int >> mask)] = result 112 | 113 | manuf_file.close() 114 | 115 | def update(self, manuf_url=None, manuf_name=None, refresh=True): 116 | """Update the Wireshark OUI database to the latest version. 117 | 118 | Args: 119 | manuf_url (str): URL pointing to OUI database. Defaults to database located at 120 | code.wireshark.org. 121 | manuf_name (str): Location to store the new OUI database. Defaults to "manuf" in the 122 | same directory. 123 | refresh (bool): Refresh the database once updated. Defaults to True. Uses database 124 | stored at manuf_name. 125 | 126 | Raises: 127 | URLError: If the download fails 128 | 129 | """ 130 | if not manuf_url: 131 | manuf_url = self.MANUF_URL 132 | if not manuf_name: 133 | manuf_name = self._manuf_name 134 | 135 | # Retrieve the new database 136 | try: 137 | response = urlopen(manuf_url) 138 | except URLError: 139 | raise URLError("Failed downloading OUI database") 140 | 141 | # Parse the response 142 | if response.code is 200: 143 | with open(manuf_name, "wb") as write_file: 144 | write_file.write(response.read()) 145 | if refresh: 146 | self.refresh(manuf_name) 147 | else: 148 | err = "{0} {1}".format(response.code, response.msg) 149 | raise URLError("Failed downloading database: {0}".format(err)) 150 | 151 | response.close() 152 | 153 | def search(self, mac, maximum=1): 154 | """Search for multiple Vendor tuples possibly matching a MAC address. 155 | 156 | Args: 157 | mac (str): MAC address in standard format. 158 | maximum (int): Maximum results to return. Defaults to 1. 159 | 160 | Returns: 161 | List of Vendor namedtuples containing (manuf, comment), with closest result first. May 162 | be empty if no results found. 163 | 164 | Raises: 165 | ValueError: If the MAC could not be parsed. 166 | 167 | """ 168 | vendors = [] 169 | if maximum <= 0: 170 | return vendors 171 | mac_str = self._strip_mac(mac) 172 | mac_int = self._get_mac_int(mac_str) 173 | 174 | # If the user only gave us X bits, check X bits. No partial matching! 175 | for mask in range(self._bits_left(mac_str), 48): 176 | result = self._masks.get((mask, mac_int >> mask)) 177 | if result: 178 | vendors.append(result) 179 | if len(vendors) >= maximum: 180 | break 181 | return vendors 182 | 183 | def get_all(self, mac): 184 | """Get a Vendor tuple containing (manuf, comment) from a MAC address. 185 | 186 | Args: 187 | mac (str): MAC address in standard format. 188 | 189 | Returns: 190 | Vendor: Vendor namedtuple containing (manuf, comment). Either or both may be None if 191 | not found. 192 | 193 | Raises: 194 | ValueError: If the MAC could not be parsed. 195 | 196 | """ 197 | vendors = self.search(mac) 198 | if len(vendors) == 0: 199 | return Vendor(manuf=None, comment=None) 200 | return vendors[0] 201 | 202 | def get_manuf(self, mac): 203 | """Returns manufacturer from a MAC address. 204 | 205 | Args: 206 | mac (str): MAC address in standard format. 207 | 208 | Returns: 209 | string: String containing manufacturer, or None if not found. 210 | 211 | Raises: 212 | ValueError: If the MAC could not be parsed. 213 | 214 | """ 215 | return self.get_all(mac).manuf 216 | 217 | def get_comment(self, mac): 218 | """Returns comment from a MAC address. 219 | 220 | Args: 221 | mac (str): MAC address in standard format. 222 | 223 | Returns: 224 | string: String containing comment, or None if not found. 225 | 226 | Raises: 227 | ValueError: If the MAC could not be parsed. 228 | 229 | """ 230 | return self.get_all(mac).comment 231 | 232 | # Gets the integer representation of a stripped mac string 233 | def _get_mac_int(self, mac_str): 234 | try: 235 | # Fill in missing bits with zeroes 236 | return int(mac_str, 16) << self._bits_left(mac_str) 237 | except ValueError: 238 | raise ValueError("Could not parse MAC: {0}".format(mac_str)) 239 | 240 | # Regular expression that matches '-', ':', and '.' characters 241 | _pattern = re.compile(r"[-:\.]") 242 | 243 | # Strips the MAC address of '-', ':', and '.' characters 244 | def _strip_mac(self, mac): 245 | return self._pattern.sub("", mac) 246 | 247 | # Gets the number of bits left in a mac string 248 | @staticmethod 249 | def _bits_left(mac_str): 250 | return 48 - 4 * len(mac_str) 251 | 252 | def main(): 253 | """Simple command line wrapping for MacParser.""" 254 | argparser = argparse.ArgumentParser(description="Parser utility for Wireshark's OUI database.") 255 | argparser.add_argument('-m', "--manuf", 256 | help="manuf file path. Defaults to manuf in same directory", 257 | action="store") 258 | argparser.add_argument("-u", "--update", 259 | help="update manuf file from the internet", 260 | action="store_true") 261 | argparser.add_argument("mac_address", nargs='?', help="MAC address to check") 262 | 263 | args = argparser.parse_args() 264 | if args.manuf: 265 | parser = MacParser(manuf_name=args.manuf, update=args.update) 266 | else: 267 | parser = MacParser(update=args.update) 268 | 269 | if args.mac_address: 270 | print(parser.get_all(args.mac_address)) 271 | 272 | sys.exit(0) 273 | 274 | if __name__ == "__main__": 275 | main() 276 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import django 4 | 5 | from scapy.all import * 6 | from scapy.layers.dot11 import Dot11, Dot11AssoReq, Dot11AssoResp, Dot11ProbeReq, Dot11ReassoReq, Dot11ReassoResp 7 | from scapy.layers.inet import UDP 8 | from scapy.layers.l2 import ARP 9 | 10 | django.setup() 11 | 12 | from packet_processing import packet_processor 13 | from packet_processing.packet_processor import client_to_ssid_list, get_manuf, ascii_printable 14 | 15 | parser = argparse.ArgumentParser(description='WiFi Passive Server') 16 | parser.add_argument('-r', dest='pcap', action='store', help='pcap file to read') 17 | parser.add_argument('-i', dest='interface', action='store', default='mon0', help='interface to sniff (default mon0)') 18 | args = parser.parse_args() 19 | 20 | logging.basicConfig(level=logging.INFO) 21 | logger = logging.getLogger(__name__) 22 | 23 | total_pkt_count = 0 24 | 25 | 26 | def filter_and_send_packet(pkt): 27 | global total_pkt_count 28 | total_pkt_count += 1 29 | 30 | # Quick way to indicate that the sniffing is still continuing 31 | if total_pkt_count % 100000 == 0: 32 | logger.info('Total Packet Count thus far: ' + str(total_pkt_count)) 33 | 34 | if pkt.haslayer(ARP): 35 | packet_processor.ingest_ARP_packet(pkt) 36 | 37 | if pkt.haslayer(Dot11ProbeReq): 38 | packet_processor.ingest_dot11_probe_req_packet(pkt) 39 | 40 | elif pkt.haslayer(Dot11AssoReq) or \ 41 | pkt.haslayer(Dot11AssoResp) or \ 42 | pkt.haslayer(Dot11ReassoReq) or \ 43 | pkt.haslayer(Dot11ReassoResp): 44 | logger.debug('Packet with Asso Req:') 45 | logger.debug('Packet Summary: ' + str(pkt.summary())) 46 | logger.debug('Packet Fields: ' + str(pkt.fields)) 47 | pass 48 | 49 | if pkt.haslayer(Dot11) and \ 50 | pkt.haslayer(UDP) and \ 51 | pkt.dst == '224.0.0.251': 52 | packet_processor.ingest_mdns_packet(pkt) 53 | 54 | 55 | if args.pcap: 56 | logger.info('Reading PCAP file %s...' % args.pcap) 57 | sniff(offline=args.pcap, prn=lambda x: filter_and_send_packet(x), store=0) 58 | else: 59 | logger.info('Realtime Sniffing on interface %s...' % args.interface) 60 | sniff(iface=args.interface, prn=lambda x: filter_and_send_packet(x), store=0) 61 | 62 | logger.info('Summary of devices detected:') 63 | 64 | for mac in client_to_ssid_list: 65 | logger.info('%s [%s] probed for %s' % (get_manuf(mac), 66 | mac, 67 | ', '.join(map(ascii_printable, client_to_ssid_list[mac])))) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'security_ssid.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /packet_processing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/packet_processing/__init__.py -------------------------------------------------------------------------------- /packet_processing/packet_processor.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | 3 | from django.core.exceptions import * 4 | from dnslib import DNSRecord 5 | from netaddr import EUI 6 | from scapy.all import * 7 | from scapy.layers.dot11 import Dot11, Dot11Elt 8 | from scapy.layers.l2 import ARP, Ether 9 | 10 | from mac_parser import manuf 11 | from security_ssid import settings 12 | from security_ssid.models import AP, Client 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | if settings.USE_INFLUX_DB: 17 | from db import influx 18 | else: 19 | logger.warning("Skipping InfluxDB data functionality.") 20 | 21 | client_to_ssid_list = defaultdict(list) 22 | mac_parser_ws = manuf.MacParser() 23 | 24 | 25 | def ingest_dot11_probe_req_packet(dot11_probe_pkt): 26 | logger.debug('Dot11 Probe Req found') 27 | client_mac = dot11_probe_pkt.getlayer(Dot11).addr2 28 | 29 | if dot11_probe_pkt.haslayer(Dot11Elt) and dot11_probe_pkt.info: 30 | try: 31 | probed_ssid = dot11_probe_pkt.info.decode('utf8') 32 | logger.debug("Probed SSID: {}".format(probed_ssid)) 33 | 34 | except UnicodeDecodeError: 35 | probed_ssid = 'HEX:%s' % binascii.hexlify(dot11_probe_pkt.info) 36 | logger.info('%s [%s] probed for non-UTF8 SSID (%s bytes, converted to "%s")' % ( 37 | get_manuf(client_mac), client_mac, len(dot11_probe_pkt.info), probed_ssid)) 38 | 39 | probed_ssid = remove_null_char_for_postgres(probed_ssid) 40 | 41 | if len(probed_ssid) > 0 and probed_ssid not in client_to_ssid_list[client_mac]: 42 | client_to_ssid_list[client_mac].append(probed_ssid) 43 | update_summary_database(client_mac=client_mac, pkt_time=dot11_probe_pkt.time, SSID=probed_ssid) 44 | 45 | if len(probed_ssid) > 0: 46 | client_signal_strength = try_to_parse_rssi_from_packet(dot11_probe_pkt) 47 | 48 | logger.info("%s [%s] probeReq for %s, " 49 | "signal strength: %s" % ( 50 | get_manuf(client_mac), 51 | client_mac, 52 | ascii_printable(probed_ssid), 53 | client_signal_strength)) 54 | 55 | send_client_data_to_influxdb(client_mac, 56 | dot11_probe_pkt.time, 57 | client_signal_strength, 58 | ascii_printable(probed_ssid)) 59 | 60 | else: 61 | logger.debug('Dot11Elt and info missing from sub packet in Dot11ProbeReq') 62 | 63 | 64 | def try_to_parse_rssi_from_packet(dot11_probe_pkt): 65 | if "notdecoded" in dot11_probe_pkt: 66 | if dot11_probe_pkt.notdecoded is not None: 67 | # The location of the RSSI strength is dependent on the physical NIC 68 | # Alfa AWUS 036N 69 | # client_signal_strength = -(256 - ord(probe_pkt.notdecoded[-4:-3])) 70 | logger.debug("Getting Signal Strength") 71 | logger.debug(dot11_probe_pkt.notdecoded) 72 | # Alfa AWUS 036NHA (Atheros AR9271) 73 | client_signal_strength = -(256 - ord(dot11_probe_pkt.notdecoded[-2:-1])) 74 | 75 | else: 76 | client_signal_strength = -100 77 | logger.debug("No client signal strength found in 'notdecoded' segment") 78 | 79 | else: 80 | client_signal_strength = -100 81 | logger.debug("NOTDECODED missing from packet, so no strength found") 82 | 83 | return client_signal_strength 84 | 85 | 86 | def ingest_ARP_packet(arp_pkt): 87 | logger.debug('ARP packet detected.') 88 | 89 | arp = arp_pkt.getlayer(ARP) 90 | dot11 = arp_pkt.getlayer(Dot11) 91 | 92 | try: 93 | # On wifi, BSSID (mac) of AP (Access Point) that the client is currently connected to 94 | target_ap_bssid = dot11.addr1 95 | 96 | # Wifi client mac address 97 | source_client_mac = dot11.addr2 98 | 99 | # While sniffing wifi (mon0), the other-AP bssid disclosure will be here in 802.11 dest 100 | target_mac = dot11.addr3 101 | 102 | if target_ap_bssid != 'ff:ff:ff:ff:ff:ff' and target_mac != 'ff:ff:ff:ff:ff:ff': 103 | if dot11.FCfield == 1 and arp.op == 1 and source_client_mac != target_mac: 104 | logger.info('%s [%s] ' + 'ARP' + ' who has %s? tell %s -> %s [%s] on BSSID %s') % \ 105 | (get_manuf(source_client_mac), source_client_mac, arp.pdst, arp.psrc, get_manuf(target_mac), 106 | target_mac, target_ap_bssid) 107 | update_summary_database(client_mac=source_client_mac, pkt_time=arp_pkt.time, BSSID=target_mac) 108 | else: 109 | logger.debug('Skipping ARP packet. Code 1') 110 | else: 111 | logger.debug('Skipping ARP packet. Code 2') 112 | 113 | except: 114 | try: 115 | if arp_pkt.haslayer(Ether): 116 | # wifi client mac when sniffing a tap interface (e.g. at0 provided by airbase-ng) 117 | source_client_mac = arp_pkt.getlayer(Ether).src 118 | 119 | # we won't get any 802.11/SSID probes but the bssid disclosure will be in the ethernet dest 120 | target_mac = arp_pkt.getlayer(Ether).dst 121 | 122 | if target_mac != 'ff:ff:ff:ff:ff:ff' and arp.op == 1: 123 | logger.info('%s [%s] ' + 'ARP' + ' who has %s? tell %s -> %s [%s] (Ether)') % \ 124 | (get_manuf(source_client_mac), source_client_mac, arp.pdst, arp.psrc, 125 | get_manuf(target_mac), target_mac) 126 | update_summary_database(client_mac=source_client_mac, pkt_time=arp_pkt.time, BSSID=target_mac) 127 | 128 | else: 129 | logger.info('Skipping ARP Ether packet. Code 3') 130 | 131 | except IndexError: 132 | pass 133 | 134 | 135 | def ingest_mdns_packet(mdns_pkt): 136 | logger.debug('Packet with Dot11, UDP, and Apple mDNS') 137 | 138 | # only parse MDNS names for 802.11 layer sniffing for now, easy to see what's a request from a client 139 | for mdns_pkt in mdns_pkt: 140 | if mdns_pkt.dport == 5353: 141 | logger.debug('Packet destination port 5353') 142 | try: 143 | d = DNSRecord.parse(mdns_pkt['Raw.load']) 144 | for q in d.questions: 145 | if q.qtype == 255 and '_tcp.local' not in str(q.qname): 146 | try: 147 | src = mdns_pkt.getlayer('Dot11').addr3 148 | name = str(q.qname).strip('.local') 149 | 150 | # An mDNS Ethernet frame is a multicast UDP packet to: 151 | # MAC address 01:00:5E:00:00:FB (for IPv4) or 33:33:00:00:00:FB (for IPv6) 152 | # IPv4 address 224.0.0.251 or IPv6 address FF02::FB 153 | # UDP port 5353 154 | if src != '01:00:5e:00:00:fb': 155 | create_or_update_client(src, datetime.utcfromtimestamp(mdns_pkt.time), name) 156 | 157 | except AttributeError: 158 | logger.error('Error parsing MDNS packet') 159 | except IndexError: 160 | pass 161 | 162 | 163 | def get_manuf(mac_addr): 164 | # Try 2 different parsers on the mac address to find the Manufacturer 165 | try: 166 | mac = EUI(mac_addr) 167 | calced_manufacturer = mac.oui.records[0]['org'].split(' ')[0].replace(',', '') 168 | except: 169 | try: 170 | calced_manufacturer = mac_parser_ws.get_manuf(mac_addr) 171 | except: 172 | calced_manufacturer = 'unknown' 173 | 174 | return ascii_printable(calced_manufacturer) 175 | 176 | 177 | def create_or_update_client(mac_addr, pkt_utc_time, name=None): 178 | try: 179 | _client = Client.objects.get(mac=mac_addr) 180 | 181 | # Check if the client device has been seen previously, and if so, update the last seen time to now 182 | if _client.lastseen_date < pkt_utc_time: 183 | _client.lastseen_date = pkt_utc_time 184 | 185 | # If the client doesn't already exist, we create a new Client to represent this device 186 | except ObjectDoesNotExist: 187 | _client = Client(mac=mac_addr, 188 | firstseen_date=pkt_utc_time, 189 | lastseen_date=pkt_utc_time, 190 | manufacturer=get_manuf(mac_addr)) 191 | 192 | if name: 193 | _client.name = name 194 | logger.debug('Updated Client name of {} to {}'.format(_client, _client.name)) 195 | 196 | _client.save() 197 | 198 | return _client 199 | 200 | 201 | def ascii_printable(s): 202 | if s is not None: 203 | return ''.join(i for i in s if ord(i) > 31 and ord(i) < 128) 204 | else: 205 | return '' 206 | 207 | 208 | def remove_null_char_for_postgres(string_to_sanitize): 209 | return string_to_sanitize.replace('\x00', '') 210 | 211 | def update_summary_database(client_mac=None, pkt_time=None, SSID='', BSSID=''): 212 | utc_pkt_time = datetime.utcfromtimestamp(pkt_time) 213 | 214 | if SSID: 215 | try: 216 | access_pt = AP.objects.get(SSID=SSID) 217 | 218 | # Create new Access Point 219 | except ObjectDoesNotExist: 220 | access_pt = AP(SSID=SSID, 221 | firstprobed_date=utc_pkt_time, 222 | lastprobed_date=utc_pkt_time, 223 | manufacturer='Unknown') 224 | 225 | elif BSSID: 226 | try: 227 | access_pt = AP.objects.get(BSSID=BSSID) 228 | 229 | # Create new Access Point 230 | except ObjectDoesNotExist: 231 | access_pt = AP(BSSID=BSSID, 232 | firstprobed_date=utc_pkt_time, 233 | lastprobed_date=utc_pkt_time, 234 | manufacturer=get_manuf(BSSID)) 235 | 236 | if access_pt.lastprobed_date and access_pt.lastprobed_date < utc_pkt_time: 237 | logger.debug('Updating Access Point Last Probed date to: {}'.format(utc_pkt_time)) 238 | access_pt.lastprobed_date = utc_pkt_time 239 | 240 | # Save here first, to avoid ValueError: 'AP' instance needs to have a primary key 241 | # value before a many-to-many relationship can be used. 242 | access_pt.save() 243 | 244 | access_pt.client.add(create_or_update_client(client_mac, utc_pkt_time)) 245 | access_pt.save() 246 | 247 | 248 | def send_client_data_to_influxdb(client_mac_addr, pkt_time, client_sig_rssi, probed_ssid_name): 249 | if settings.USE_INFLUX_DB: 250 | influx_formatted_data = influx.assemble_json(measurement=settings.INFLUX_DB_MEASUREMENT_NAME, 251 | pkt_timestamp=pkt_time, 252 | rssi_value=client_sig_rssi, 253 | client_mac_addr=client_mac_addr, 254 | probed_ssid=probed_ssid_name) 255 | influx.write_data(influx_formatted_data) 256 | 257 | logger.debug('{} Client data sent to influxdb'.format(client_mac_addr)) 258 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | export tzinfo=utc 2 | export DJANGO_SETTINGS_MODULE=security_ssid.settings 3 | python ./main.py $* 4 | -------------------------------------------------------------------------------- /security_ssid/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/security_ssid/__init__.py -------------------------------------------------------------------------------- /security_ssid/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Client, AP, Location 4 | 5 | 6 | class ClientAdmin(admin.ModelAdmin): 7 | list_display = ['manufacturer', 'mac', 'lastseen_date', 'name'] 8 | 9 | 10 | class APAdmin(admin.ModelAdmin): 11 | list_display = ['SSID', 'BSSID', 'manufacturer', 'lastprobed_date', 'name'] 12 | 13 | 14 | admin.site.register(Client, ClientAdmin) 15 | admin.site.register(AP, APAdmin) 16 | admin.site.register(Location) 17 | -------------------------------------------------------------------------------- /security_ssid/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for security_ssid project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'security_ssid.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /security_ssid/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.db import models 4 | from picklefield.fields import PickledObjectField 5 | 6 | 7 | class Client(models.Model): 8 | mac = models.CharField(max_length=len('ff:ff:ff:ff:ff:ff'), unique=True) 9 | firstseen_date = models.DateTimeField('UTC Datetime first seen') 10 | lastseen_date = models.DateTimeField('UTC Datetime last seen') 11 | name = models.CharField(max_length=200, blank=True) 12 | comment = models.CharField(max_length=200, blank=True) 13 | manufacturer = models.CharField(max_length=200, blank=True) 14 | 15 | def __unicode__(self): 16 | return u'%s' % (self.mac) 17 | 18 | class Meta: 19 | app_label = 'security_ssid' 20 | 21 | 22 | # Access Point 23 | class AP(models.Model): 24 | client = models.ManyToManyField(Client) 25 | SSID = models.CharField(max_length=200, blank=True) 26 | BSSID = models.CharField(max_length=len('ff:ff:ff:ff:ff:ff'), blank=True) 27 | name = models.CharField(max_length=200, blank=True) 28 | comment = models.CharField(max_length=200, blank=True) 29 | manufacturer = models.CharField(max_length=200, blank=True) 30 | firstprobed_date = models.DateTimeField('UTC Datetime first probed for') 31 | lastprobed_date = models.DateTimeField('UTC Datetime last probed for') 32 | lon = models.FloatField(null=True) 33 | lat = models.FloatField(null=True) 34 | address = models.CharField(max_length=200, blank=True) 35 | 36 | def __unicode__(self): 37 | if self.SSID and self.BSSID: 38 | return u'%s [%s]' % (self.SSID, self.BSSID) 39 | if self.SSID: 40 | return u'%s' % self.SSID 41 | if self.BSSID: 42 | return u'ARP:%s' % self.BSSID 43 | 44 | class Meta: 45 | app_label = 'security_ssid' 46 | 47 | class PointDB(models.Model): 48 | name = models.CharField(max_length=200, unique=True) 49 | date_saved = models.DateTimeField('Datetime saved', default=datetime.now) 50 | pointdict = PickledObjectField() 51 | 52 | class Meta: 53 | app_label = 'security_ssid' 54 | 55 | class Location(models.Model): 56 | ap = models.ForeignKey(AP, on_delete=models.CASCADE) 57 | lon = models.FloatField() 58 | lat = models.FloatField() 59 | name = models.CharField(max_length=200, blank=True) 60 | source = models.CharField(max_length=20) # Apple or Wigle at present 61 | comment = models.CharField(max_length=200, blank=True) 62 | 63 | def __unicode__(self): 64 | return u'%s,%s' % (self.lon, self.lat) 65 | 66 | class Meta: 67 | app_label = 'security_ssid' -------------------------------------------------------------------------------- /security_ssid/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | 6 | # Not used for local sqlite DB, but is used for remote Postgres connection 7 | DJANGO_DB_HOST = '10.0.0.13' 8 | 9 | USE_INFLUX_DB = False 10 | INFLUX_HOST = '10.0.0.13' 11 | INFLUX_PORT = '8086' 12 | INFLUX_USER = '' 13 | INFLUX_PASSWORD = '' 14 | INFLUX_DB = 'securityssid' 15 | INFLUX_TIMEOUT_SEC = 5.0 16 | INFLUX_DB_MEASUREMENT_NAME = 'clientdevices' 17 | 18 | wigle_username = '' 19 | wigle_password = '' 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = 'CHANGETHISFORPROD!' 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = ['*'] 31 | 32 | # Python dotted path to the WSGI application used by Django's runserver. 33 | WSGI_APPLICATION = 'security_ssid.wsgi.application' 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 'security_ssid', 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 'django.contrib.sites' 46 | ] 47 | 48 | MIDDLEWARE = [ 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | ] 57 | 58 | ROOT_URLCONF = 'security_ssid.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'security_ssid.wsgi.application' 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.sqlite3', 84 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 85 | } 86 | # 'default': { 87 | # 'ENGINE': 'django.db.backends.postgresql', 88 | # # postgres exists by default, if you change this to a real DB name, the DB will 89 | # # need to be created manually 90 | # 'NAME': 'postgres', 91 | # 'USER': 'postgres', 92 | # 'PASSWORD': 'postgres', 93 | # 'HOST': DJANGO_DB_HOST, 94 | # 'PORT': '5432', 95 | # } 96 | } 97 | 98 | # Password validation 99 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 100 | 101 | AUTH_PASSWORD_VALIDATORS = [ 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 113 | }, 114 | ] 115 | 116 | # Internationalization 117 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 118 | 119 | LANGUAGE_CODE = 'en-us' 120 | 121 | TIME_ZONE = 'America/New_York' 122 | 123 | USE_I18N = True 124 | 125 | USE_L10N = True 126 | 127 | USE_TZ = False 128 | 129 | # Static files (CSS, JavaScript, Images) 130 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 131 | 132 | STATIC_URL = '/static/' 133 | -------------------------------------------------------------------------------- /security_ssid/static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/security_ssid/static/ajax-loader.gif -------------------------------------------------------------------------------- /security_ssid/static/bluff-min.js: -------------------------------------------------------------------------------- 1 | Bluff={VERSION:'0.3.6',array:function(c){if(c.length===undefined)return[c];var d=[],f=c.length;while(f--)d[f]=c[f];return d},array_new:function(c,d){var f=[];while(c--)f.push(d);return f},each:function(c,d,f){for(var g=0,h=c.length;gthis._5)?g.length:this._5;Bluff.each(g,function(c,d){if(c===undefined)return;if(this.maximum_value===null&&this.minimum_value===null)this.maximum_value=this.minimum_value=c;this.maximum_value=this._1k(c)?c:this.maximum_value;if(this.maximum_value>=0)this._c=true;this.minimum_value=this._1G(c)?c:this.minimum_value;if(this.minimum_value<0)this._c=true},this)},draw:function(){if(this.stacked)this._1H();this._1I();this._x(function(){this._0.rectangle(this.left_margin,this.top_margin,this._b-this.right_margin,this._L-this.bottom_margin);this._0.rectangle(this._2,this._7,this._l,this._g)})},clear:function(){this._M()},on:function(c,d,f){var g=this._10[c]=this._10[c]||[];g.push([d,f])},trigger:function(d,f){var g=this._10[d];if(!g)return;Bluff.each(g,function(c){c[0].call(c[1],f)})},_1I:function(){if(!this._c)return this._1J();this._16();this._1K();if(this.sort)this._1L();this._1M();this._N();this._1N();this._1O()},_16:function(g){if(this._9===null||g===true){this._9=[];if(!this._c)return;this._1l();Bluff.each(this._1,function(d){var f=[];Bluff.each(d[this.klass.DATA_VALUES_INDEX],function(c){if(c===null||c===undefined)f.push(null);else f.push((c-this.minimum_value)/this._i)},this);this._9.push([d[this.klass.DATA_LABEL_INDEX],f,d[this.klass.DATA_COLOR_INDEX]])},this)}},_1l:function(){this._i=this.maximum_value-this.minimum_value;this._i=this._i>0?this._i:1;var c=Math.round(Math.LOG10E*Math.log(this._i));this._1m=Math.pow(10,3-c)},_1K:function(){this._O=this.hide_line_markers?0:this._P(this.marker_font_size);this._1n=this.hide_title?0:this._P(this.title_font_size);this._1o=this.hide_legend?0:this._P(this.legend_font_size);var c,d,f,g,h,i,j;if(this.hide_line_markers){this._2=this.left_margin;this._17=this.right_margin;this._1p=this.bottom_margin}else{d=0;if(this.has_left_labels){c='';for(j in this.labels){c=c.length>this.labels[j].length?c:this.labels[j]}d=this._H(this.marker_font_size,c)*1.25}else{d=this._H(this.marker_font_size,this._Q(this.maximum_value))}f=this.hide_line_numbers&&!this.has_left_labels?0.0:d+this.klass.LABEL_MARGIN*2;this._2=this.left_margin+f+(this.y_axis_label===null?0.0:this._O+this.klass.LABEL_MARGIN*2);g=-Infinity;for(j in this.labels)g=g>Number(j)?g:Number(j);g=Math.round(g);h=(g>=(this._5-1)&&this.center_labels_over_point)?this._H(this.marker_font_size,this.labels[g])/2:0;this._17=this.right_margin+h;this._1p=this.bottom_margin+this._O+this.klass.LABEL_MARGIN}this._l=this._b-this._17;this._6=this._b-this._2-this._17;this._7=this.top_margin+(this.hide_title?this.title_margin:this._1n+this.title_margin)+(this.hide_legend?this.legend_margin:this._1o+this.legend_margin);i=(this.x_axis_label===null)?0.0:this._O+this.klass.LABEL_MARGIN;this._g=this._L-this._1p-i;this._3=this._g-this._7},_1N:function(){if(this.x_axis_label){var c=this._g+this.klass.LABEL_MARGIN*2+this._O;this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='north';this._0.annotate_scaled(this._b,1.0,0.0,c,this.x_axis_label,this._a);this._x(function(){this._0.line(0.0,c,this._b,c)})}},_N:function(){if(this.hide_line_markers)return;if(this.y_axis_increment===null){if(this.marker_count===null){Bluff.each([3,4,5,6,7],function(c){if(!this.marker_count&&this._i%c===0)this.marker_count=c},this);this.marker_count=this.marker_count||4}this._18=(this._i>0)?this._19(this._i/this.marker_count):1}else{this.maximum_value=Math.max(Math.ceil(this.maximum_value),this.y_axis_increment);this.minimum_value=Math.floor(this.minimum_value);this._1l();this._16(true);this.marker_count=Math.round(this._i/this.y_axis_increment);this._18=this.y_axis_increment}this._1P=this._3/(this._i/this._18);var d,f,g,h;for(d=0,f=this.marker_count;d<=f;d++){g=this._7+this._3-d*this._1P;this._0.stroke=this.marker_color;this._0.stroke_width=1;this._0.line(this._2,g,this._l,g);h=d*this._18+this.minimum_value;if(!this.hide_line_numbers){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.font_weight='normal';this._0.stroke='transparent';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='east';this._0.annotate_scaled(this._2-this.klass.LABEL_MARGIN,1.0,0.0,g,this._Q(h),this._a)}}},_1q:function(c){return(this._b-c)/2},_1M:function(){if(this.hide_legend)return;this._I=Bluff.map(this._1,function(c){return c[this.klass.DATA_LABEL_INDEX]},this);var i=this.legend_box_size;if(this.font)this._0.font=this.font;this._0.pointsize=this.legend_font_size;var j=[[]];Bluff.each(this._I,function(c){var d=j.length-1;var f=this._0.get_type_metrics(c);var g=f.width+i*2.7;j[d].push(g);if(Bluff.sum(j[d])>(this._b*0.9))j.push([j[d].pop()])},this);var k=this._1q(Bluff.sum(j[0]));var m=this.hide_title?this.top_margin+this.title_margin:this.top_margin+this.title_margin+this._1n;this._x(function(){this._0.stroke_width=1;this._0.line(0,m,this._b,m)});Bluff.each(this._I,function(c,d){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.pointsize=this._d(this.legend_font_size);this._0.stroke='transparent';this._0.font_weight='normal';this._0.gravity='west';this._0.annotate_scaled(this._b,1.0,k+(i*1.7),m,c,this._a);this._0.stroke='transparent';this._0.fill=this._1[d][this.klass.DATA_COLOR_INDEX];this._0.rectangle(k,m-i/2.0,k+i,m+i/2.0);this._0.pointsize=this.legend_font_size;var f=this._0.get_type_metrics(c);var g=f.width+(i*2.7),h;j[0].shift();if(j[0].length==0){this._x(function(){this._0.line(0.0,m,this._b,m)});j.shift();if(j.length>0)k=this._1q(Bluff.sum(j[0]));h=Math.max(this._1o,i)+this.legend_margin;if(j.length>0){m+=h;this._7+=h;this._3=this._g-this._7}}else{k+=g}},this);this._w=0},_1O:function(){if(this.hide_title||!this.title)return;this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.pointsize=this._d(this.title_font_size);this._0.font_weight='bold';this._0.gravity='north';this._0.annotate_scaled(this._b,1.0,0,this.top_margin,this.title,this._a)},_e:function(c,d){if(this.hide_line_markers)return;var f;if(this.labels[d]&&!this._u[d]){f=this._g+this.klass.LABEL_MARGIN;this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.font_weight='normal';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='north';this._0.annotate_scaled(1.0,1.0,c,f,this.labels[d],this._a);this._u[d]=true;this._x(function(){this._0.stroke_width=1;this._0.line(0.0,f,this._b,f)})}},_y:function(d,f,g,h,i,j,k,m){if(!this.tooltips)return;var n=this._0.tooltip(d,f,g,h,i,j,k);Bluff.Event.observe(n,'click',function(){var c={series:i,label:this.labels[m],value:k,color:j};this.trigger('click:datapoint',c)},this)},_1J:function(){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.font_weight='normal';this._0.pointsize=this._d(80);this._0.gravity='center';this._0.annotate_scaled(this._b,this._L/2,0,10,this.no_data_message,this._a)},_M:function(){var c=this._k.background_colors;switch(true){case c instanceof Array:this._1Q.apply(this,c);break;case typeof c==='string':this._1R(c);break;default:this._1S(this._k.background_image);break}},_1R:function(c){this._0.render_solid_background(this._j,this._t,c)},_1Q:function(c,d){this._0.render_gradiated_background(this._j,this._t,c,d)},_1S:function(c){},_1j:function(){this._w=0;this._u={};this._k={};this._0.scale(this._a,this._a)},_2k:function(c){return this._a*c},_d:function(c){var d=c*this._a;return d},_R:function(c,d){return(c>d)?d:c},_1k:function(c,d){return c>this.maximum_value},_1G:function(c,d){return c100){c/=10;d*=10}return Math.floor(c)*d},_1L:function(){var f=this._1T,g=this.klass.DATA_VALUES_INDEX;this._9.sort(function(c,d){return f(d[g])-f(c[g])});this._1.sort(function(c,d){return f(d[g])-f(c[g])})},_1T:function(d){var f=0;Bluff.each(d,function(c){f+=(c||0)});return f},_1H:function(){var g=[],h=this._5;while(h--)g[h]=0;Bluff.each(this._1,function(f){Bluff.each(f[this.klass.DATA_VALUES_INDEX],function(c,d){g[d]+=c},this);f[this.klass.DATA_VALUES_INDEX]=Bluff.array(g)},this)},_x:function(c){if(this.klass.DEBUG){this._0.fill='transparent';this._0.stroke='turquoise';c.call(this)}},_1F:function(){var c=this._w;this._w=(this._w+1)%this.colors.length;return this.colors[c]},_Q:function(c){var d=this.klass.THOUSAND_SEPARATOR,f=(this._i%this.marker_count==0||this.y_axis_increment!==null)?String(Math.round(c)):String(Math.floor(c*this._1m)/this._1m);var g=f.split('.');g[0]=g[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g,'$1'+d);return g.join('.')},_P:function(c){return this._0.caps_height(c)},_H:function(c,d){return this._0.text_width(c,d)}});Bluff.Area=new JS.Class(Bluff.Base,{draw:function(){this.callSuper();if(!this._c)return;this._S=this._6/(this._5-1);this._0.stroke='transparent';Bluff.each(this._9,function(h){var i=[],j=0.0,k=0.0;Bluff.each(h[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this._S*d);var g=this._7+(this._3-c*this._3);if(j>0&&k>0){i.push(f);i.push(g)}else{i.push(this._2);i.push(this._g-1);i.push(f);i.push(g)}this._e(f,d);j=f;k=g},this);i.push(this._l);i.push(this._g-1);i.push(this._2);i.push(this._g-1);this._0.fill=h[this.klass.DATA_COLOR_INDEX];this._0.polyline(i)},this)}});Bluff.BarConversion=new JS.Class({mode:null,zero:null,graph_top:null,graph_height:null,minimum_value:null,spread:null,getLeftYRightYscaled:function(c,d){var f;switch(this.mode){case 1:d[0]=this.graph_top+this.graph_height*(1-c)+1;d[1]=this.graph_top+this.graph_height-1;break;case 2:d[0]=this.graph_top+1;d[1]=this.graph_top+this.graph_height*(1-c)-1;break;case 3:f=c-this.minimum_value/this.spread;if(c>=this.zero){d[0]=this.graph_top+this.graph_height*(1-(f-this.zero))+1;d[1]=this.graph_top+this.graph_height*(1-this.zero)-1}else{d[0]=this.graph_top+this.graph_height*(1-(f-this.zero))+1;d[1]=this.graph_top+this.graph_height*(1-this.zero)-1}break;default:d[0]=0.0;d[1]=0.0}}});Bluff.Bar=new JS.Class(Bluff.Base,{bar_spacing:0.9,draw:function(){this.center_labels_over_point=(Bluff.keys(this.labels).length>this._5);this.callSuper();if(!this._c)return;this._T()},_T:function(){this._8=this._6/(this._5*this._1.length);var n=(this._8*(1-this.bar_spacing))/2;this._0.stroke_opacity=0.0;var l=new Bluff.BarConversion();l.graph_height=this._3;l.graph_top=this._7;if(this.minimum_value>=0){l.mode=1}else{if(this.maximum_value<=0){l.mode=2}else{l.mode=3;l.spread=this._i;l.minimum_value=this.minimum_value;l.zero=-this.minimum_value/this._i}}Bluff.each(this._9,function(j,k){var m=this._1[k][this.klass.DATA_VALUES_INDEX];Bluff.each(j[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this._8*(k+d+((this._1.length-1)*d)))+n;var g=f+this._8*this.bar_spacing;var h=[];l.getLeftYRightYscaled(c,h);this._0.fill=j[this.klass.DATA_COLOR_INDEX];this._0.rectangle(f,h[0],g,h[1]);this._y(f,h[0],g-f,h[1]-h[0],j[this.klass.DATA_LABEL_INDEX],j[this.klass.DATA_COLOR_INDEX],m[d],d);var i=this._2+(this._1.length*this._8*d)+(this._1.length*this._8/2.0);this._e(i-(this.center_labels_over_point?this._8/2.0:0.0),d)},this)},this);if(this.center_labels_over_point)this._e(this._l,this._5)}});Bluff.Line=new JS.Class(Bluff.Base,{baseline_value:null,baseline_color:null,line_width:null,dot_radius:null,hide_dots:null,hide_lines:null,initialize:function(c){if(arguments.length>3)throw'Wrong number of arguments';if(arguments.length===1||(typeof arguments[1]!=='number'&&typeof arguments[1]!=='string'))this.callSuper(c,null);else this.callSuper();this.hide_dots=this.hide_lines=false;this.baseline_color='red';this.baseline_value=null},draw:function(){this.callSuper();if(!this._c)return;this.x_increment=(this._5>1)?(this._6/(this._5-1)):this._6;var l;if(this._U!==undefined){l=this._7+(this._3-this._U*this._3);this._0.push();this._0.stroke=this.baseline_color;this._0.fill_opacity=0.0;this._0.stroke_width=3.0;this._0.line(this._2,l,this._2+this._6,l);this._0.pop()}Bluff.each(this._9,function(i,j){var k=null,m=null;var n=this._1[j][this.klass.DATA_VALUES_INDEX];this._1U=this._1V(i);Bluff.each(i[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this.x_increment*d);if(typeof c!=='number')return;this._e(f,d);var g=this._7+(this._3-c*this._3);this._0.stroke=i[this.klass.DATA_COLOR_INDEX];this._0.fill=i[this.klass.DATA_COLOR_INDEX];this._0.stroke_opacity=1.0;this._0.stroke_width=this.line_width||this._R(this._j/(this._9[0][this.klass.DATA_VALUES_INDEX].length*6),3.0);var h=this.dot_radius||this._R(this._j/(this._9[0][this.klass.DATA_VALUES_INDEX].length*2),7.0);if(!this.hide_lines&&k!==null&&m!==null){this._0.line(k,m,f,g)}else if(this._1U){this._0.circle(f,g,f-h,g)}if(!this.hide_dots)this._0.circle(f,g,f-h,g);this._y(f-h,g-h,2*h,2*h,i[this.klass.DATA_LABEL_INDEX],i[this.klass.DATA_COLOR_INDEX],n[d],d);k=f;m=g},this)},this)},_16:function(){this.maximum_value=Math.max(this.maximum_value,this.baseline_value);this.callSuper();if(this.baseline_value!==null)this._U=this.baseline_value/this.maximum_value},_1V:function(d){var f=0;Bluff.each(d[this.klass.DATA_VALUES_INDEX],function(c){if(c!==undefined)f+=1});return f===1}});Bluff.Dot=new JS.Class(Bluff.Base,{draw:function(){this.has_left_labels=true;this.callSuper();if(!this._c)return;var k=1.0;this._J=this._3/this._5;this._1a=this._J*k/this._9.length;this._0.stroke_opacity=0.0;var m=Bluff.array_new(this._5,0),n=Bluff.array_new(this._5,this._2),l=(this._J*(1-k))/2;Bluff.each(this._9,function(i,j){Bluff.each(i[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(c*this._6)-Math.round(this._1a/6.0);var g=this._7+(this._J*d)+l+Math.round(this._1a/2.0);if(j===0){this._0.stroke=this.marker_color;this._0.stroke_width=1.0;this._0.opacity=0.1;this._0.line(this._2,g,this._2+this._6,g)}this._0.fill=i[this.klass.DATA_COLOR_INDEX];this._0.stroke='transparent';this._0.circle(f,g,f+Math.round(this._1a/3.0),g);var h=this._7+(this._J*d+this._J/2)+l;this._e(h,d)},this)},this)},_N:function(){if(this.hide_line_markers)return;this._0.stroke_antialias=false;this._0.stroke_width=1;var c=5;var d=this._19(this.maximum_value/c);for(var f=0;f<=c;f++){var g=(this._l-this._2)/c,h=this._l-(g*f)-1,i=f-c,j=Math.abs(i)*d;this._0.stroke=this.marker_color;this._0.line(h,this._g,h,this._g+0.5*this.klass.LABEL_MARGIN);if(!this.hide_line_numbers){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='center';this._0.annotate_scaled(0,0,h,this._g+(this.klass.LABEL_MARGIN*2.0),j,this._a)}this._0.stroke_antialias=true}},_e:function(c,d){if(this.labels[d]&&!this._u[d]){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.font_weight='normal';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='east';this._0.annotate_scaled(1,1,this._2-this.klass.LABEL_MARGIN*2.0,c,this.labels[d],this._a);this._u[d]=true}}});Bluff.Net=new JS.Class(Bluff.Base,{hide_dots:null,line_width:null,dot_radius:null,initialize:function(){this.callSuper();this.hide_dots=false;this.hide_line_numbers=true},draw:function(){this.callSuper();if(!this._c)return;this._z=this._3/2.0;this._A=this._2+(this._6/2.0);this._B=this._7+(this._3/2.0)-10;this._S=this._6/(this._5-1);var s=this.dot_radius||this._R(this._j/(this._9[0][this.klass.DATA_VALUES_INDEX].length*2.5),7.0);this._0.stroke_opacity=1.0;this._0.stroke_width=this.line_width||this._R(this._j/(this._9[0][this.klass.DATA_VALUES_INDEX].length*4),3.0);var r;if(this._U!==undefined){r=this._7+(this._3-this._U*this._3);this._0.push();this._0.stroke_color=this.baseline_color;this._0.fill_opacity=0.0;this._0.stroke_width=5;this._0.line(this._2,r,this._2+this._6,r);this._0.pop()}Bluff.each(this._9,function(o){var p=null,q=null;Bluff.each(o[this.klass.DATA_VALUES_INDEX],function(c,d){if(c===undefined)return;var f=d*Math.PI*2/this._5,g=c*this._z,h=this._A+Math.sin(f)*g,i=this._B-Math.cos(f)*g,j=(d+10){this._0.fill=c[this.klass.DATA_COLOR_INDEX];var f=(c[this.klass.DATA_VALUES_INDEX][0]/o)*360;this._0.circle(n,l,n+k,l,p,p+f+0.5);var g=p+((p+f)-p)/2,h=Math.round((c[this.klass.DATA_VALUES_INDEX][0]/o)*100.0),i;if(h>=this.hide_labels_less_than){i=this._Q(c[this.klass.DATA_VALUES_INDEX][0]);this._e(n,l,g,k+(k*this.klass.TEXT_OFFSET_PERCENTAGE),i,c,d)}p+=f}},this)},_e:function(c,d,f,g,h,i,j){var k=20.0,m=c,n=d,l=g+k,o=l*0.15,p=m+((l+o)*Math.cos(f*Math.PI/180)),q=n+(l*Math.sin(f*Math.PI/180));this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.pointsize=this._d(this.marker_font_size);this._0.font_weight='bold';this._0.gravity='center';this._0.annotate_scaled(0,0,p,q,h,this._a);this._y(p-20,q-20,40,40,i[this.klass.DATA_LABEL_INDEX],i[this.klass.DATA_COLOR_INDEX],h,j)},_1W:function(){var d=0;Bluff.each(this._1,function(c){d+=c[this.klass.DATA_VALUES_INDEX][0]},this);return d}});Bluff.SideBar=new JS.Class(Bluff.Base,{bar_spacing:0.9,draw:function(){this.has_left_labels=true;this.callSuper();if(!this._c)return;this._T()},_T:function(){this._V=this._3/this._5;this._8=this._V/this._9.length;this._0.stroke_opacity=0.0;var q=Bluff.array_new(this._5,0),s=Bluff.array_new(this._5,this._2),r=(this._8*(1-this.bar_spacing))/2;Bluff.each(this._9,function(l,o){var p=this._1[o][this.klass.DATA_VALUES_INDEX];Bluff.each(l[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this._6-c*this._6-q[d]),g=this._2+this._6-q[d],h=g-f,i=s[d]-1,j=this._7+(this._V*d)+(this._8*o)+r,k=i+h,m=j+this._8*this.bar_spacing;q[d]+=(c*this._6);this._0.stroke='transparent';this._0.fill=l[this.klass.DATA_COLOR_INDEX];this._0.rectangle(i,j,k,m);this._y(i,j,k-i,m-j,l[this.klass.DATA_LABEL_INDEX],l[this.klass.DATA_COLOR_INDEX],p[d],d);var n=this._7+(this._V*d+this._V/2);this._e(n,d)},this)},this)},_N:function(){if(this.hide_line_markers)return;this._0.stroke_antialias=false;this._0.stroke_width=1;var c=5;var d=this._19(this._i/c),f,g,h,i;for(var j=0;j<=c;j++){f=(this._l-this._2)/c;g=this._l-(f*j)-1;h=j-c;i=Math.abs(h)*d+this.minimum_value;this._0.stroke=this.marker_color;this._0.line(g,this._g,g,this._7);if(!this.hide_line_numbers){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='center';this._0.annotate_scaled(0,0,g,this._g+(this.klass.LABEL_MARGIN*2.0),this._Q(i),this._a)}}},_e:function(c,d){if(this.labels[d]&&!this._u[d]){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.stroke='transparent';this._0.font_weight='normal';this._0.pointsize=this._d(this.marker_font_size);this._0.gravity='east';this._0.annotate_scaled(1,1,this._2-this.klass.LABEL_MARGIN*2.0,c,this.labels[d],this._a);this._u[d]=true}}});Bluff.Spider=new JS.Class(Bluff.Base,{hide_text:null,hide_axes:null,transparent_background:null,initialize:function(c,d,f){this.callSuper(c,f);this._1X=d;this.hide_legend=true},draw:function(){this.hide_line_markers=true;this.callSuper();if(!this._c)return;var c=this._3,d=this._3/2.0,f=this._2+(this._6-c)/2.0,g=this._2+(this._6/2.0),h=this._7+(this._3/2.0)-25;this._1Y=d/this._1X;var i=this._1Z(),j=0.0,k=(2*Math.PI)/this._1.length,m=0.0;if(!this.hide_axes)this._20(g,h,d,k);this._21(g,h,k)},_1s:function(c){return c*this._1Y},_e:function(c,d,f,g,h){var i=50,j=c,k=d+0,m=j+((g+i)*Math.cos(f)),n=k+((g+i)*Math.sin(f));this._0.fill=this.marker_color;if(this.font)this._0.font=this.font;this._0.pointsize=this._d(this.legend_font_size);this._0.stroke='transparent';this._0.font_weight='bold';this._0.gravity='center';this._0.annotate_scaled(0,0,m,n,h,this._a)},_20:function(g,h,i,j,k){if(this.hide_axes)return;var m=0.0;Bluff.each(this._1,function(c){this._0.stroke=k||c[this.klass.DATA_COLOR_INDEX];this._0.stroke_width=5.0;var d=i*Math.cos(m);var f=i*Math.sin(m);this._0.line(g,h,g+d,h+f);if(!this.hide_text)this._e(g,h,m,i,c[this.klass.DATA_LABEL_INDEX]);m+=j},this)},_21:function(d,f,g,h){var i=[],j=0.0;Bluff.each(this._1,function(c){i.push(d+this._1s(c[this.klass.DATA_VALUES_INDEX][0])*Math.cos(j));i.push(f+this._1s(c[this.klass.DATA_VALUES_INDEX][0])*Math.sin(j));j+=g},this);this._0.stroke_width=1.0;this._0.stroke=h||this.marker_color;this._0.fill=h||this.marker_color;this._0.fill_opacity=0.4;this._0.polyline(i)},_1Z:function(){var d=0.0;Bluff.each(this._1,function(c){d+=c[this.klass.DATA_VALUES_INDEX][0]},this);return d}});Bluff.Base.StackedMixin=new JS.Module({_1b:function(){var g={};Bluff.each(this._1,function(f){Bluff.each(f[this.klass.DATA_VALUES_INDEX],function(c,d){if(!g[d])g[d]=0.0;g[d]+=c},this)},this);for(var h in g){if(g[h]>this.maximum_value)this.maximum_value=g[h]}this.minimum_value=0}});Bluff.StackedArea=new JS.Class(Bluff.Base,{include:Bluff.Base.StackedMixin,last_series_goes_on_bottom:null,draw:function(){this._1b();this.callSuper();if(!this._c)return;this._S=this._6/(this._5-1);this._0.stroke='transparent';var n=Bluff.array_new(this._5,0);var l=null;var o=this.last_series_goes_on_bottom?'reverse_each':'each';Bluff[o](this._9,function(h){var i=l;l=[];Bluff.each(h[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this._S*d);var g=this._7+(this._3-c*this._3-n[d]);n[d]+=(c*this._3);l.push(f);l.push(g);this._e(f,d)},this);var j,k,m;if(i){j=Bluff.array(l);for(k=i.length/2-1;k>=0;k--){j.push(i[2*k]);j.push(i[2*k+1])}j.push(l[0]);j.push(l[1])}else{j=Bluff.array(l);j.push(this._l);j.push(this._g-1);j.push(this._2);j.push(this._g-1);j.push(l[0]);j.push(l[1])}this._0.fill=h[this.klass.DATA_COLOR_INDEX];this._0.polyline(j)},this)}});Bluff.StackedBar=new JS.Class(Bluff.Base,{include:Bluff.Base.StackedMixin,bar_spacing:0.9,draw:function(){this._1b();this.callSuper();if(!this._c)return;this._8=this._6/this._5;var l=(this._8*(1-this.bar_spacing))/2;this._0.stroke_opacity=0.0;var o=Bluff.array_new(this._5,0);Bluff.each(this._9,function(k,m){var n=this._1[m][this.klass.DATA_VALUES_INDEX];Bluff.each(k[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this._8*d)+(this._8*this.bar_spacing/2.0);this._e(f,d);if(c==0)return;var g=this._2+(this._8*d)+l;var h=this._7+(this._3-c*this._3-o[d])+1;var i=g+this._8*this.bar_spacing;var j=this._7+this._3-o[d]-1;o[d]+=(c*this._3);this._0.fill=k[this.klass.DATA_COLOR_INDEX];this._0.rectangle(g,h,i,j);this._y(g,h,i-g,j-h,k[this.klass.DATA_LABEL_INDEX],k[this.klass.DATA_COLOR_INDEX],n[d],d)},this)},this)}});Bluff.AccumulatorBar=new JS.Class(Bluff.StackedBar,{draw:function(){if(this._1.length!==1)throw'Incorrect number of datasets';var g=[],h=0,i=[];Bluff.each(this._1[0][this.klass.DATA_VALUES_INDEX],function(d){var f=-Infinity;Bluff.each(i,function(c){f=Math.max(f,c)});i.push((h>0)?(d+f):d);g.push(i[h]-d);h+=1},this);this.data("Accumulator",g);this.callSuper()}});Bluff.SideStackedBar=new JS.Class(Bluff.SideBar,{include:Bluff.Base.StackedMixin,bar_spacing:0.9,draw:function(){this.has_left_labels=true;this._1b();this.callSuper()},_T:function(){this._8=this._3/this._5;var q=Bluff.array_new(this._5,0),s=Bluff.array_new(this._5,this._2),r=(this._8*(1-this.bar_spacing))/2;Bluff.each(this._9,function(l,o){var p=this._1[o][this.klass.DATA_VALUES_INDEX];Bluff.each(l[this.klass.DATA_VALUES_INDEX],function(c,d){var f=this._2+(this._6-c*this._6-q[d])+1;var g=this._2+this._6-q[d]-1;var h=g-f;this._0.fill=l[this.klass.DATA_COLOR_INDEX];var i=s[d],j=this._7+(this._8*d)+r,k=i+h,m=j+this._8*this.bar_spacing;s[d]+=h;q[d]+=(c*this._6-2);this._0.rectangle(i,j,k,m);this._y(i,j,k-i,m-j,l[this.klass.DATA_LABEL_INDEX],l[this.klass.DATA_COLOR_INDEX],p[d],d);var n=this._7+(this._8*d)+(this._8*this.bar_spacing/2.0);this._e(n,d)},this)},this)},_1k:function(c,d){d=d||0;return this._1r(c,d)>this.maximum_value},_1r:function(d,f){var g=0;Bluff.each(this._1,function(c){g+=c[this.klass.DATA_VALUES_INDEX][f]},this);return g}});Bluff.Mini.Legend=new JS.Module({hide_mini_legend:false,_1c:function(){if(this.hide_mini_legend)return;this._I=Bluff.map(this._1,function(c){return c[this.klass.DATA_LABEL_INDEX]},this);var d=this._d(this._1.length*this._1t()+this.top_margin+this.bottom_margin);this._22=this._L;this._23=this._b;switch(this.legend_position){case'right':this._t=Math.max(this._t,d);this._j+=this._24()+this.left_margin;break;default:this._t+=d;break}this._M()},_1t:function(){return this._P(this.legend_font_size)*1.7},_24:function(){var d=0;Bluff.each(this._I,function(c){d=Math.max(this._H(this.legend_font_size,c),d)},this);return this._d(d+40*1.7)},_1d:function(){if(this.hide_mini_legend)return;var f=40.0,g=10.0,h=100.0,i=40.0;if(this.font)this._0.font=this.font;this._0.pointsize=this.legend_font_size;var j,k;switch(this.legend_position){case'right':j=this._23+this.left_margin;k=this.top_margin+i;break;default:j=h,k=this._22+i;break}this._x(function(){this._0.line(0.0,k,this._b,k)});Bluff.each(this._I,function(c,d){this._0.fill=this.font_color;if(this.font)this._0.font=this.font;this._0.pointsize=this._d(this.legend_font_size);this._0.stroke='transparent';this._0.font_weight='normal';this._0.gravity='west';this._0.annotate_scaled(this._b,1.0,j+(f*1.7),k,this._25(c),this._a);this._0.stroke='transparent';this._0.fill=this._1[d][this.klass.DATA_COLOR_INDEX];this._0.rectangle(j,k-f/2.0,j+f,k+f/2.0);k+=this._1t()},this);this._w=0},_25:function(c){var d=String(c);while(this._H(this._d(this.legend_font_size),d)>(this._j-this.legend_left_margin-this.right_margin)&&(d.length>1))d=d.substr(0,d.length-1);return d+(d.length=1?(c*i):1;var k=(d*i)>=1?(d*i):1;var h=this._W(this.pointsize,h);h.style.color=this.fill;h.style.cursor='default';h.style.fontWeight=this.font_weight;h.style.textAlign='center';h.style.left=(this._f*f+this._26(h,j))+'px';h.style.top=(this._h*g+this._27(h,k))+'px'},tooltip:function(d,f,g,h,i,j,k){if(g<0)d+=g;if(h<0)f+=h;var m=this._n.parentNode,n=document.createElement('div');n.className=this.klass.TARGET_CLASS;n.style.cursor='default';n.style.position='absolute';n.style.left=(this._f*d-3)+'px';n.style.top=(this._h*f-3)+'px';n.style.width=(this._f*Math.abs(g)+5)+'px';n.style.height=(this._h*Math.abs(h)+5)+'px';n.style.fontSize=0;n.style.overflow='hidden';Bluff.Event.observe(n,'mouseover',function(c){Bluff.Tooltip.show(i,j,k)});Bluff.Event.observe(n,'mouseout',function(c){Bluff.Tooltip.hide()});m.appendChild(n);return n},circle:function(c,d,f,g,h,i){var j=Math.sqrt(Math.pow(f-c,2)+Math.pow(g-d,2));var k=0,m=2*Math.PI;this._4.fillStyle=this.fill;this._4.beginPath();if(h!==undefined&&i!==undefined&&Math.abs(Math.floor(i-h))!==360){k=h*Math.PI/180;m=i*Math.PI/180;this._4.moveTo(this._f*(c+j*Math.cos(m)),this._h*(d+j*Math.sin(m)));this._4.lineTo(this._f*c,this._h*d);this._4.lineTo(this._f*(c+j*Math.cos(k)),this._h*(d+j*Math.sin(k)))}this._4.arc(this._f*c,this._h*d,this._f*j,k,m,false);this._4.fill()},line:function(c,d,f,g){this._4.strokeStyle=this.stroke;this._4.lineWidth=this.stroke_width;this._4.beginPath();this._4.moveTo(this._f*c,this._h*d);this._4.lineTo(this._f*f,this._h*g);this._4.stroke()},polyline:function(c){this._4.fillStyle=this.fill;this._4.globalAlpha=this.fill_opacity||1;try{this._4.strokeStyle=this.stroke}catch(e){}var d=c.shift(),f=c.shift();this._4.beginPath();this._4.moveTo(this._f*d,this._h*f);while(c.length>0){d=c.shift();f=c.shift();this._4.lineTo(this._f*d,this._h*f)}this._4.fill()},rectangle:function(c,d,f,g){var h;if(c>f){h=c;c=f;f=h}if(d>g){h=d;d=g;g=h}try{this._4.fillStyle=this.fill;this._4.fillRect(this._f*c,this._h*d,this._f*(f-c),this._h*(g-d))}catch(e){}try{this._4.strokeStyle=this.stroke;if(this.stroke!=='transparent')this._4.strokeRect(this._f*c,this._h*d,this._f*(f-c),this._h*(g-d))}catch(e){}},_26:function(c,d){var f=this._K(c).width;switch(this.gravity){case'west':return 0;case'east':return d-f;case'north':case'south':case'center':return(d-f)/2}},_27:function(c,d){var f=this._K(c).height;switch(this.gravity){case'north':return 0;case'south':return d-f;case'west':case'east':case'center':return(d-f)/2}},_1u:function(){var c=this._n.parentNode;if(c.className===this.klass.WRAPPER_CLASS)return c;c=document.createElement('div');c.className=this.klass.WRAPPER_CLASS;c.style.position='relative';c.style.border='none';c.style.padding='0 0 0 0';this._n.parentNode.insertBefore(c,this._n);c.appendChild(this._n);return c},_W:function(c,d){var f=this._28(d);f.style.fontFamily=this.font;f.style.fontSize=(typeof c==='number')?c+'px':c;return f},_28:function(c){var d=document.createElement('div');d.className=this.klass.TEXT_CLASS;d.style.position='absolute';d.appendChild(document.createTextNode(c));this._1u().appendChild(d);return d},_X:function(c){c.parentNode.removeChild(c);if(c.className===this.klass.TARGET_CLASS)Bluff.Event.stopObserving(c)},_K:function(c){var d=c.style.display;return(d&&d!=='none')?{width:c.offsetWidth,height:c.offsetHeight}:{width:c.clientWidth,height:c.clientHeight}}});Bluff.Event={_Y:[],_1v:(window.attachEvent&&navigator.userAgent.indexOf('Opera')===-1),observe:function(d,f,g,h){var i=Bluff.map(this._1w(d,f),function(c){return c._29});if(Bluff.index(i,g)!==-1)return;var j=function(c){g.call(h||null,d,Bluff.Event._2a(c))};this._Y.push({_Z:d,_1e:f,_29:g,_1x:j});if(d.addEventListener)d.addEventListener(f,j,false);else d.attachEvent('on'+f,j)},stopObserving:function(d){var f=d?this._1w(d):this._Y;Bluff.each(f,function(c){if(c._Z.removeEventListener)c._Z.removeEventListener(c._1e,c._1x,false);else c._Z.detachEvent('on'+c._1e,c._1x)})},_1w:function(d,f){var g=[];Bluff.each(this._Y,function(c){if(d&&c._Z!==d)return;if(f&&c._1e!==f)return;g.push(c)});return g},_2a:function(c){if(!this._1v)return c;if(!c)return false;if(c._2b)return c;c._2b=true;var d=this._2c(c);c.target=c.srcElement;c.pageX=d.x;c.pageY=d.y;return c},_2c:function(c){var d=document.documentElement,f=document.body||{scrollLeft:0,scrollTop:0};return{x:c.pageX||(c.clientX+(d.scrollLeft||f.scrollLeft)-(d.clientLeft||0)),y:c.pageY||(c.clientY+(d.scrollTop||f.scrollTop)-(d.clientTop||0))}}};if(Bluff.Event._1v)window.attachEvent('onunload',function(){Bluff.Event.stopObserving();Bluff.Event._Y=null});if(navigator.userAgent.indexOf('AppleWebKit/')>-1)window.addEventListener('unload',function(){},false);Bluff.Tooltip=new JS.Singleton({LEFT_OFFSET:20,TOP_OFFSET:-6,DATA_LENGTH:8,CLASS_NAME:'bluff-tooltip',setup:function(){this._o=document.createElement('div');this._o.className=this.CLASS_NAME;this._o.style.position='absolute';this.hide();document.body.appendChild(this._o);Bluff.Event.observe(document.body,'mousemove',function(c,d){this._o.style.left=(d.pageX+this.LEFT_OFFSET)+'px';this._o.style.top=(d.pageY+this.TOP_OFFSET)+'px'},this)},show:function(c,d,f){f=Number(String(f).substr(0,this.DATA_LENGTH));this._o.innerHTML='  '+c+' '+f+'';this._o.style.display=''},hide:function(){this._o.style.display='none'}});Bluff.Event.observe(window,'load',Bluff.Tooltip.method('setup'));Bluff.TableReader=new JS.Class({NUMBER_FORMAT:/\-?(0|[1-9]\d*)(\.\d+)?(e[\+\-]?\d+)?/i,initialize:function(c,d){this._1f=d||{};this._2d=this._1f.orientation||'auto';this._2e=(typeof c==='string')?document.getElementById(c):c},get_data:function(){if(!this._1)this._1y();return this._1},get_labels:function(){if(!this._1g)this._1y();return this._1g},get_title:function(){return this._2f},get_series:function(c){if(this._1[c])return this._1[c];return this._1[c]={points:[]}},_1y:function(){this._C=this._m=0;this._p=this._q=0;this._1=[];this._1g={};this._r=[];this._s=[];this._1h=[];this._1i=[];this._1z(this._2e);this._2g();this._2h();Bluff.each(this._s,function(c,d){this.get_series(d-this._q).name=c},this);Bluff.each(this._r,function(c,d){this._1g[d-this._p]=c},this)},_1z:function(c){this._2i(c);var d,f=c.childNodes,g=f.length;for(d=0;d=0)return true;var h=f.length;while(h--){if(Bluff.index(g,f[h])>=0)return true}return false},_2g:function(){var d=this._1i.length,f;while(d--){f=this._1i[d];if(f<=this._q)continue;this._s.splice(f-1,1);if(f>=this._q)this._1.splice(f-1-this._q,1)}var d=this._1h.length,f;while(d--){f=this._1h[d];if(f<=this._p)continue;this._r.splice(f-1,1);Bluff.each(this._1,function(c){if(f>=this._p)c.points.splice(f-1-this._p,1)},this)}},_2h:function(){switch(this._2d){case'auto':if((this._r.length>1&&this._s.length===1)||this._r.length]+>/gi,'')},extend:{Mixin:new JS.Module({data_from_table:function(d,f){var g=new Bluff.TableReader(d,f),h=g.get_data();Bluff.each(h,function(c){this.data(c.name,c.points)},this);this.labels=g.get_labels();this.title=g.get_title()||this.title}})}});Bluff.Base.include(Bluff.TableReader.Mixin); -------------------------------------------------------------------------------- /security_ssid/static/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'trebuchet MS', 'Lucida sans', Arial; 3 | font-size: 14px; 4 | color: #444; 5 | margin: 10px; 6 | } 7 | 8 | table { 9 | *border-collapse: collapse; /* IE7 and lower */ 10 | border-spacing: 0; 11 | width: 95%; 12 | table-layout: fixed; 13 | overflow: hidden; 14 | } 15 | 16 | .bordered { 17 | border: solid #ccc 1px; 18 | -moz-border-radius: 6px; 19 | -webkit-border-radius: 6px; 20 | border-radius: 6px; 21 | -webkit-box-shadow: 0 1px 1px #ccc; 22 | -moz-box-shadow: 0 1px 1px #ccc; 23 | box-shadow: 0 1px 1px #ccc; 24 | } 25 | 26 | .bordered tr:hover { 27 | background: #fbf8e9; 28 | -o-transition: all 0.1s ease-in-out; 29 | -webkit-transition: all 0.1s ease-in-out; 30 | -moz-transition: all 0.1s ease-in-out; 31 | -ms-transition: all 0.1s ease-in-out; 32 | transition: all 0.1s ease-in-out; 33 | } 34 | 35 | .bordered td, .bordered th { 36 | border-left: 1px solid #ccc; 37 | border-top: 1px solid #ccc; 38 | padding: 10px; 39 | text-align: left; 40 | } 41 | 42 | .bordered th { 43 | background-color: #dce9f9; 44 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebf3fc), to(#dce9f9)); 45 | background-image: -webkit-linear-gradient(top, #ebf3fc, #dce9f9); 46 | background-image: -moz-linear-gradient(top, #ebf3fc, #dce9f9); 47 | background-image: -ms-linear-gradient(top, #ebf3fc, #dce9f9); 48 | background-image: -o-linear-gradient(top, #ebf3fc, #dce9f9); 49 | background-image: linear-gradient(top, #ebf3fc, #dce9f9); 50 | -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset; 51 | -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset; 52 | box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset; 53 | border-top: none; 54 | text-shadow: 0 1px 0 rgba(255, 255, 255, .5); 55 | } 56 | 57 | .bordered td:first-child, .bordered th:first-child { 58 | border-left: none; 59 | } 60 | 61 | .bordered th:first-child { 62 | -moz-border-radius: 6px 0 0 0; 63 | -webkit-border-radius: 6px 0 0 0; 64 | border-radius: 6px 0 0 0; 65 | } 66 | 67 | .bordered th:last-child { 68 | -moz-border-radius: 0 6px 0 0; 69 | -webkit-border-radius: 0 6px 0 0; 70 | border-radius: 0 6px 0 0; 71 | } 72 | 73 | .bordered th:only-child { 74 | -moz-border-radius: 6px 6px 0 0; 75 | -webkit-border-radius: 6px 6px 0 0; 76 | border-radius: 6px 6px 0 0; 77 | } 78 | 79 | .bordered tr:last-child td:first-child { 80 | -moz-border-radius: 0 0 0 6px; 81 | -webkit-border-radius: 0 0 0 6px; 82 | border-radius: 0 0 0 6px; 83 | } 84 | 85 | .bordered tr:last-child td:last-child { 86 | -moz-border-radius: 0 0 6px 0; 87 | -webkit-border-radius: 0 0 6px 0; 88 | border-radius: 0 0 6px 0; 89 | } 90 | 91 | /*----------------------*/ 92 | 93 | .zebra td, .zebra th { 94 | padding: 5px; 95 | border-bottom: 1px solid #f2f2f2; 96 | } 97 | 98 | .zebra tbody tr:nth-child(even) { 99 | background: #f5f5f5; 100 | -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset; 101 | -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset; 102 | box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset; 103 | } 104 | 105 | .zebra th { 106 | text-align: left; 107 | text-shadow: 0 1px 0 rgba(255, 255, 255, .5); 108 | border-bottom: 1px solid #ccc; 109 | background-color: #eee; 110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#eee)); 111 | background-image: -webkit-linear-gradient(top, #f5f5f5, #eee); 112 | background-image: -moz-linear-gradient(top, #f5f5f5, #eee); 113 | background-image: -ms-linear-gradient(top, #f5f5f5, #eee); 114 | background-image: -o-linear-gradient(top, #f5f5f5, #eee); 115 | background-image: linear-gradient(top, #f5f5f5, #eee); 116 | } 117 | 118 | .zebra th:first-child { 119 | -moz-border-radius: 6px 0 0 0; 120 | -webkit-border-radius: 6px 0 0 0; 121 | border-radius: 6px 0 0 0; 122 | } 123 | 124 | .zebra th:last-child { 125 | -moz-border-radius: 0 6px 0 0; 126 | -webkit-border-radius: 0 6px 0 0; 127 | border-radius: 0 6px 0 0; 128 | } 129 | 130 | .zebra th:only-child { 131 | -moz-border-radius: 6px 6px 0 0; 132 | -webkit-border-radius: 6px 6px 0 0; 133 | border-radius: 6px 6px 0 0; 134 | } 135 | 136 | .zebra tfoot td { 137 | border-bottom: 0; 138 | border-top: 1px solid #fff; 139 | background-color: #f1f1f1; 140 | } 141 | 142 | .zebra tfoot td:first-child { 143 | -moz-border-radius: 0 0 0 6px; 144 | -webkit-border-radius: 0 0 0 6px; 145 | border-radius: 0 0 0 6px; 146 | } 147 | 148 | .zebra tfoot td:last-child { 149 | -moz-border-radius: 0 0 6px 0; 150 | -webkit-border-radius: 0 0 6px 0; 151 | border-radius: 0 0 6px 0; 152 | } 153 | 154 | .zebra tfoot td:only-child { 155 | -moz-border-radius: 0 0 6px 6px; 156 | -webkit-border-radius: 0 0 6px 6px; 157 | border-radius: 0 0 6px 6px 158 | } 159 | -------------------------------------------------------------------------------- /security_ssid/static/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/security_ssid/static/flag.png -------------------------------------------------------------------------------- /security_ssid/static/js-class.js: -------------------------------------------------------------------------------- 1 | this.JS=this.JS||{};JS.extend=function(a,b){b=b||{};for(var c in b){if(a[c]===b[c])continue;a[c]=b[c]}return a};JS.extend(JS,{makeFunction:function(){return function(){return this.initialize?(this.initialize.apply(this,arguments)||this):this}},makeBridge:function(a){var b=function(){};b.prototype=a.prototype;return new b},bind:function(){var a=JS.array(arguments),b=a.shift(),c=a.shift()||null;return function(){return b.apply(c,a.concat(JS.array(arguments)))}},callsSuper:function(a){return a.SUPER===undefined?a.SUPER=/\bcallSuper\b/.test(a.toString()):a.SUPER},mask:function(a){var b=a.toString().replace(/callSuper/g,'super');a.toString=function(){return b};return a},array:function(a){if(!a)return[];if(a.toArray)return a.toArray();var b=a.length,c=[];while(b--)c[b]=a[b];return c},indexOf:function(a,b){for(var c=0,d=a.length;c':''),d=this.__meta__=new JS.Module(c?c+'.':'',{},{_1:this});d.include(this.klass.__mod__,false);return d},equals:function(a){return this===a},extend:function(a,b){return this.__eigen__().include(a,b,{_2:this})},hash:function(){return this.__hashcode__=this.__hashcode__||JS.Kernel.getHashCode()},isA:function(a){return this.__eigen__().includes(a)},method:function(a){var b=this,c=b.__mcache__=b.__mcache__||{};if((c[a]||{}).fn===b[a])return c[a].bd;return(c[a]={fn:b[a],bd:JS.bind(b[a],b)}).bd},methods:function(){return this.__eigen__().instanceMethods(true)},tap:function(a,b){a.call(b||null,this);return this}}),{__hashIndex__:0,getHashCode:function(){this.__hashIndex__+=1;return(Math.floor(new Date().getTime()/1000)+this.__hashIndex__).toString(16)}});JS.Module.include(JS.Kernel);JS.extend(JS.Module,JS.Kernel.__fns__);JS.Class.include(JS.Kernel);JS.extend(JS.Class,JS.Kernel.__fns__);JS.Interface=new JS.Class({initialize:function(d){this.test=function(a,b){var c=d.length;while(c--){if(!JS.isFn(a[d[c]]))return b?d[c]:false}return true}},extend:{ensure:function(){var a=JS.array(arguments),b=a.shift(),c,d;while(c=a.shift()){d=c.test(b,true);if(d!==true)throw new Error('object does not implement '+d+'()');}}}});JS.Singleton=new JS.Class({initialize:function(a,b,c){return new(new JS.Class(a,b,c))}}); 2 | -------------------------------------------------------------------------------- /security_ssid/static/locate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/security_ssid/static/locate.png -------------------------------------------------------------------------------- /security_ssid/static/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/security_ssid/static/wifi.png -------------------------------------------------------------------------------- /security_ssid/templates/ap_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block head %} 4 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |

Clients probing for network {{ object|safe }} ({{ object.manufacturer }})

12 | {% if object.name %} 13 |

Name: {{ object.name }} 14 |

{% endif %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for c in Clients %} 23 | 24 | 25 | 26 | 27 | {% endfor %} 28 |
MACName
{{ c.mac }}{{ c.name }}
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /security_ssid/templates/ap_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load dict_lookup %} 3 | {% load tz %} 4 | 5 | {% block head %} 6 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 |

{{ devicecount }} devices probing for {{ apcount }} networks detected

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% for ap in object_list %} 23 | 24 | 31 | 36 | {% localtime on %} 37 | 38 | {% endlocaltime %} 39 | 40 | {% endfor %} 41 |
SSID / BSSIDProbed for byLast probed for
25 | {% url "network" ap.BSSID as bssidurl %} 26 | {% url "network" ap.SSID as ssidurl %} 27 | 28 | {% if ap.BSSID %}{{ ap.BSSID }}{% else %}{{ ap.SSID }}{% endif %}{% if ap.BSSID %} ( 29 | {{ ap.manufacturer }}){% endif %} 30 | {{ ap.client.all|length }} | 32 | {% for c in ap.client.all %} 33 | {% if forloop.counter < 6 %}{{ c.mac }}{% endif %} 34 | {% if forloop.counter == 6 %}...{% endif %} 35 | {% endfor %}{{ ap.lastprobed_date }}
42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /security_ssid/templates/apple-mobile-ajax.js: -------------------------------------------------------------------------------- 1 | {% 2 | load 3 | dict_lookup % 4 | } 5 | {% 6 | load 7 | dot_colour % 8 | } 9 | 10 | {% 11 | for b in bssids %} 12 | var myLatLng = new google.maps.LatLng 13 | { 14 | { 15 | apdict | key 16 | : 17 | b 18 | } 19 | } 20 | ; 21 | apData.push(myLatLng); 22 | var marker = new google.maps.Marker({ 23 | position: myLatLng, 24 | map: map, 25 | title: '{{manufdict|key:b}}', 26 | icon: { % 27 | if b == bssid % 28 | } 29 | bigRedDot 30 | {%else% 31 | } 32 | { 33 | { 34 | manufdict | key 35 | : 36 | b | dot_colour 37 | } 38 | } 39 | {% 40 | endif % 41 | } 42 | }) 43 | ; 44 | google.maps.event.addListener(marker, "click", function () { 45 | loadAjax("{{b}}") 46 | }); 47 | markersArray.push(marker); 48 | {% 49 | endfor % 50 | } 51 | 52 | pointArray = new google.maps.MVCArray(apData); 53 | 54 | if (heatmap.getMap()) { 55 | heatmap.setMap(null); 56 | heatmap = new google.maps.visualization.HeatmapLayer({data: pointArray}); 57 | heatmap.setMap(map); 58 | } 59 | 60 | document.getElementById('bssid').innerHTML = 'Cell {{bssid}}'; 61 | document.getElementById('results').innerHTML = markersArray.length + ' cells (' + { 62 | { 63 | hits 64 | } 65 | } 66 | +' added)'; -------------------------------------------------------------------------------- /security_ssid/templates/apple-mobile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load dict_lookup %} 3 | {% load dot_colour %} 4 | 5 | {% block head %} 6 | 7 | 8 | 15 | 16 | 17 | 228 | {% endblock %} 229 | 230 | {% block content %} 231 |

232 |
Cell {{ bssid }}
233 |

234 | 237 |

238 |
{% if hits %}{{ hits }} cells shown.{% endif %}
239 |

240 |
241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 |
249 |
250 | {% endblock %} 251 | -------------------------------------------------------------------------------- /security_ssid/templates/apple-wloc-ajax.js: -------------------------------------------------------------------------------- 1 | {% 2 | load 3 | dict_lookup % 4 | } 5 | 6 | {% 7 | for b in bssids %} 8 | var myLatLng = new google.maps.LatLng 9 | { 10 | { 11 | apdict | key 12 | : 13 | b 14 | } 15 | } 16 | ; 17 | apData.push(myLatLng); 18 | var marker = new google.maps.Marker({ 19 | position: myLatLng, 20 | map: map, 21 | title: '{{b}} - {{manufdict|key:b}}', 22 | icon: { % 23 | if b == bssid % 24 | } 25 | redDot 26 | {%else% 27 | } 28 | blueDot 29 | {% 30 | endif % 31 | } 32 | }) 33 | ; 34 | google.maps.event.addListener(marker, "click", function () { 35 | loadAjax("{{b}}") 36 | }); 37 | markersArray.push(marker); 38 | {% 39 | endfor % 40 | } 41 | 42 | pointArray = new google.maps.MVCArray(apData); 43 | 44 | if (heatmap.getMap()) { 45 | heatmap.setMap(null); 46 | heatmap = new google.maps.visualization.HeatmapLayer({data: pointArray}); 47 | heatmap.setMap(map); 48 | } 49 | 50 | document.getElementById('bssid').innerHTML = 'BSSID {{bssid}}'; 51 | document.getElementById('results').innerHTML = markersArray.length + ' APs (' + { 52 | { 53 | hits 54 | } 55 | } 56 | +' added)'; -------------------------------------------------------------------------------- /security_ssid/templates/apple-wloc.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load dict_lookup %} 3 | 4 | {% block head %} 5 | 6 | 7 | 14 | 15 | 16 | 185 | {% endblock %} 186 | 187 | {% block content %} 188 |

189 |
BSSID {{ bssid }}
190 |

191 | 194 |

195 |
{% if hits %}{{ hits }} APs shown.{% endif %}
196 |

197 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 |
208 |
209 | {% endblock %} 210 | -------------------------------------------------------------------------------- /security_ssid/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load urldecode %} 2 | 3 | 4 | 5 | {% block title %}SSID ABI v2.0{% endblock %} 6 | {% block css %} 7 | 8 | {% endblock %} 9 | {% block head %}{% endblock %} 10 | 11 | 12 | 13 | 22 | {% block content %}{% endblock %} 23 | 24 | 25 | -------------------------------------------------------------------------------- /security_ssid/templates/client_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block head %} 4 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |

Client {{ object.mac }} ({{ object.manufacturer }})

12 | {% if object.name %} 13 |

Name: {{ object.name }} 14 |

{% endif %} 15 | Probed for:

16 | {% load tz %} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for ap in APs %} 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% localtime on %} 37 | 38 | {% endlocaltime %} 39 | 40 | 45 | 46 | {% endfor %} 47 |
SSIDBSSIDLatLonCommentLast probe observedLocate
{% if ap.SSID %}{{ ap.SSID }}{% endif %}{% if ap.BSSID %}{{ ap.BSSID }}{% endif %}{% if ap.lat %} {{ ap.lat }} {% endif %}{% if ap.lon %} {{ ap.lon }} {% endif %}{{ ap.comment }}{{ ap.lastprobed_date }} 41 | {% if ap.SSID %}{% endif %} 42 | {% if ap.BSSID %}{% endif %} 43 | 44 |
48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /security_ssid/templates/client_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load dict_lookup %} 3 | 4 | {% block head %} 5 | 9 | {% endblock %} 10 | 11 | {% block content %} 12 | 13 |

{{ devicecount }} devices probing for {{ apcount }} networks detected (Client List)

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% for client in object_list %} 24 | 25 | 26 | 27 | 31 | 32 | 33 | {% endfor %} 34 |
MAC | NameManufacturerProbed forLast detected
{{ client.mac }}{% if client.name %}
{{ client.name }}{% endif %}
{{ client.manufacturer }}{% for ap in probedict|key:client %} 28 | {{ ap.SSID|safe }}{% if probedict|key:client|length > 1 and forloop.last == False %};{% endif %} 29 | {% endfor %} 30 | {{ client.lastseen_date }}
35 | 36 | 37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /security_ssid/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block head %} 4 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |

Hi

12 | {% if devicecount %}

Total: {{ devicecount }}

{% endif %} 13 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /security_ssid/templates/stats.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block head %} 4 | 5 | 6 | 32 | {% endblock %} 33 | 34 | {% block content %} 35 |

Top 10 Device Manufacturers

36 |

Total Devices: {{ devicecount }}

37 | 38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /security_ssid/templates/wigle-wloc.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load dict_lookup %} 3 | 4 | {% block head %} 5 | 6 | 7 | 14 | 15 | 16 | 52 | {% endblock %} 53 | 54 | {% block content %} 55 |

SSID

56 | 57 | 59 | {% if hits %}{{ hits }} APs returned.{% endif %} 60 |
61 | {% endblock %} 62 | -------------------------------------------------------------------------------- /security_ssid/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GISDev01/security-ssid-abi/e8e9825a764322c8dfb021c1d4665c5b2d0d8662/security_ssid/templatetags/__init__.py -------------------------------------------------------------------------------- /security_ssid/templatetags/dict_lookup.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.filter 7 | def key(d, key): 8 | return d[key] 9 | -------------------------------------------------------------------------------- /security_ssid/templatetags/dot_colour.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | 6 | @register.filter 7 | def dot_colour(operator): 8 | d = 'blue' 9 | if 'Optus' in operator: 10 | d = 'yellow' 11 | if 'Vodafone' in operator: 12 | d = 'red' 13 | if 'Three' in operator: 14 | d = 'red' 15 | 16 | if 'CID:-1' in operator: 17 | return d + 'LAC' 18 | 19 | return d + 'Cell' 20 | -------------------------------------------------------------------------------- /security_ssid/templatetags/urldecode.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import unquote 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def urldecode(value): 10 | return unquote(value) 11 | -------------------------------------------------------------------------------- /security_ssid/urls.py: -------------------------------------------------------------------------------- 1 | """security_ssid URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.urls import re_path as url 17 | from django.contrib import admin 18 | from django.urls import path 19 | 20 | from .views import APDetail, APList, AppleMobile, AppleWloc, ClientDetail, \ 21 | ClientList, LoadDB, SaveDB, locateSSID, stats, updateSSID 22 | 23 | urlpatterns = [ 24 | path('admin/', admin.site.urls), 25 | url(r'^$', ClientList.as_view(), name="clientlist"), 26 | url(r'^client/(?P[:\w]+)$', ClientDetail.as_view(), name="client"), 27 | url(r'^clients/?$', ClientList.as_view()), 28 | url(r'^network/(?P.+)$', APDetail.as_view(), name="network"), 29 | url(r'^networks/?$', APList.as_view(), name="networks"), 30 | url(r'^apple-wloc/?$', AppleWloc, name="applewloc-base"), 31 | url(r'^savedb/(?P[:\w]*)$', SaveDB, name="savedb"), 32 | url(r'^loaddb/(?P[:\w]*)$', LoadDB, name="loaddb"), 33 | url(r'^apple-wloc/(?P[:\w]+)$', AppleWloc, name="applewloc"), 34 | url(r'^apple-mobile/(?P[:\w-]*)$', AppleMobile, name="apple-mobile"), 35 | url(r'^apple-mobile-lte/(?P[:\w-]*)$', AppleMobile, {'LTE': True}, 36 | name="apple-mobile-lte"), 37 | url(r'^updateSSID$', updateSSID, name="updatessid"), 38 | url(r'^locateSSID/?$', locateSSID, name="locatessid-base"), 39 | url(r'^locateSSID/(?P[\w\W]+)$', locateSSID, name="locatessid"), 40 | url(r'^stats/?$', stats.as_view(), name="stats"), 41 | ] 42 | -------------------------------------------------------------------------------- /security_ssid/views.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.core.exceptions import * 4 | from django.db.models import Count 5 | from django.http import HttpResponse 6 | from django.shortcuts import render 7 | from django.views.generic import * 8 | 9 | from netaddr import EUI 10 | 11 | from location_utils import wigle_query, wloc 12 | from security_ssid.models import AP, Client, PointDB 13 | 14 | 15 | def get_manuf(apdict): 16 | manufdict = {} 17 | for m in apdict.keys(): 18 | try: 19 | mac = EUI(m) 20 | manufdict[m] = mac.oui.records[0]['org'] 21 | # .split(' ')[0].replace(',','') 22 | # .replace(', Inc','').replace(' Inc.','') 23 | except: 24 | manufdict[m] = 'unknown' 25 | return manufdict 26 | 27 | 28 | class ClientList(ListView): 29 | model = Client 30 | template_name = 'client_list.html' 31 | 32 | def get_queryset(self): 33 | return Client.objects.order_by('manufacturer', 'mac') 34 | 35 | def get_context_data(self, **kwargs): 36 | context = super(ClientList, self).get_context_data(**kwargs) 37 | probedict = {} 38 | for client in Client.objects.all(): 39 | probedict[client] = AP.objects.filter(client=client) 40 | context['probedict'] = probedict 41 | context['apcount'] = len(AP.objects.all()) 42 | context['devicecount'] = len(Client.objects.all()) 43 | return context 44 | 45 | 46 | class ClientDetail(DetailView): 47 | model = Client 48 | slug_field = 'mac' 49 | template_name = 'client_detail.html' 50 | 51 | def get_context_data(self, **kwargs): 52 | context = super(ClientDetail, self).get_context_data(**kwargs) 53 | context['APs'] = AP.objects.filter(client=self.object) 54 | return context 55 | 56 | 57 | class APList(ListView): 58 | model = AP 59 | template_name = 'ap_list.html' 60 | 61 | def get_queryset(self): 62 | return AP.objects.annotate(num_clients=Count('client')).order_by('-num_clients') 63 | 64 | def get_context_data(self, **kwargs): 65 | context = super(APList, self).get_context_data(**kwargs) 66 | context['apcount'] = len(AP.objects.all()) 67 | context['devicecount'] = len(Client.objects.all()) 68 | # context['Clients'] = self.object.client 69 | return context 70 | 71 | 72 | class APDetail(DetailView): 73 | model = AP 74 | template_name = 'ap_detail.html' 75 | 76 | def get_object(self): 77 | lookup = self.kwargs['ssid_or_bssid'] 78 | if re.match(r'\w\w:\w\w:\w\w:\w\w:\w\w:\w\w', lookup): 79 | a = AP.objects.get(BSSID=lookup) 80 | else: 81 | a = AP.objects.get(SSID=lookup) 82 | return a 83 | 84 | def get_context_data(self, **kwargs): 85 | print(self.kwargs) 86 | context = super(APDetail, self).get_context_data(**kwargs) 87 | context['Clients'] = self.object.client.all() 88 | return context 89 | 90 | 91 | class Home(TemplateView): 92 | template_name = "home.html" 93 | 94 | 95 | class stats(TemplateView): 96 | template_name = "stats.html" 97 | 98 | def get_context_data(self, **kwargs): 99 | from operator import itemgetter 100 | context = super(stats, self).get_context_data(**kwargs) 101 | manuf = {} 102 | for m in Client.objects.values_list('manufacturer', flat=True).distinct(): 103 | m = m[0].upper() + (m[1:].lower()) 104 | manuf[m] = len(Client.objects.filter(manufacturer__iexact=m)) 105 | l = [] 106 | for m in manuf.items(): 107 | l.append(m) 108 | context['mac_db'] = sorted(l, key=itemgetter(1), reverse=True)[:10] 109 | context['devicecount'] = len(Client.objects.all()) 110 | return context 111 | 112 | 113 | def getCenter(apdict): 114 | numresults = len(apdict) 115 | latCenter = 0.0 116 | lonCenter = 0.0 117 | for (lat, lon) in apdict.values(): 118 | latCenter += lat 119 | lonCenter += lon 120 | try: 121 | return (((latCenter / numresults), (lonCenter / numresults))) 122 | except ZeroDivisionError: 123 | return ((0, 0)) 124 | 125 | 126 | def AppleWloc(request, bssid=None): 127 | if not bssid: 128 | print("Setting to Default BSSID") 129 | bssid = '00:1e:52:7a:ae:ad' 130 | 131 | print('Apple WLOC request for BSSID: {}'.format(bssid)) 132 | 133 | if request.GET.get('ajax'): 134 | template = 'apple-wloc-ajax.js' 135 | else: 136 | template = 'apple-wloc.html' 137 | request.session['apdict'] = {} 138 | # Reset server-side cache of unique bssids if we load page normally 139 | request.session['apset'] = [] 140 | 141 | bssid = bssid.lower() 142 | apdict = wloc.QueryBSSID(bssid) 143 | 144 | dupes = 0 145 | for ap in apdict.keys(): 146 | if ap in request.session['apset']: 147 | dupes += 1 148 | del apdict[ap] 149 | request.session['apset'].append(ap) 150 | 151 | numresults = len(apdict) 152 | 153 | print('%s dupes excluded' % dupes) 154 | print('%s in set post filter' % len(request.session['apset'])) 155 | print('%s returned to browser post filter' % numresults) 156 | 157 | if numresults == 0 or (-180.0, -180.0) in apdict.values(): 158 | print('0 WLOC results.') 159 | return HttpResponse('No valid WLOC results for BSSID: {}'.format(bssid)) 160 | else: 161 | print('Not 0 WLOC results') 162 | 163 | if bssid in apdict.keys(): 164 | try: 165 | a = AP.objects.get(BSSID=bssid) # original design - only save ap to db if it's one that has been probed for 166 | (a.lat, a.lon) = apdict[bssid] 167 | a.save() # if Apple returns a match for BSSID we save this as location 168 | print('Updated %s location to %s' % (a, (a.lat, a.lon))) 169 | except ObjectDoesNotExist: 170 | pass 171 | for key in apdict.keys(): 172 | request.session['apdict'][key] = apdict[key] 173 | print('Session apdict: %s' % len(request.session['apdict'])) 174 | return render(request, template, 175 | {'bssid': bssid, 'hits': len(apdict), 'center': getCenter(apdict), 'bssids': apdict.keys(), 176 | 'apdict': apdict, 'manufdict': get_manuf(apdict)}) 177 | 178 | 179 | def LoadDB(request, name=None): 180 | c = PointDB.objects.get(name=name) 181 | request.session['apdict'] = c.pointdict 182 | apdict = request.session['apdict'] 183 | request.session['apset'] = set(apdict.keys()) 184 | print('Loaded saved DB %s from %s' % (name, c.date_saved)) 185 | return render(request, 'apple-wloc.html', 186 | {'bssid': apdict.keys()[0], 'hits': len(apdict), 'center': getCenter(apdict), 'bssids': apdict.keys(), 187 | 'apdict': apdict, 'manufdict': get_manuf(apdict)}) 188 | 189 | 190 | def SaveDB(request, name=None): 191 | try: 192 | c = PointDB.objects.get(name=name) 193 | except ObjectDoesNotExist: 194 | c = PointDB(name=name) 195 | c.pointdict = request.session['apdict'] 196 | c.save() 197 | return HttpResponse('Saved %s points as %s' % (len(request.session['apdict'].keys()), name)) # xss 198 | 199 | 200 | def AppleMobile(request, cellid=None, LTE=False): 201 | return HttpResponse('This is currently disabled.') 202 | 203 | if 'cellset' not in request.session: 204 | request.session['cellset'] = set() 205 | if request.GET.get('ajax'): 206 | template = 'apple-mobile-ajax.js' 207 | else: 208 | template = 'apple-mobile.html' 209 | request.session['cellset'] = set() 210 | if cellid: 211 | (celldict, celldesc) = wloc.QueryMobile(cellid, LTE) 212 | numresults = len(celldict) 213 | if numresults == 0: 214 | return HttpResponse('0 results.') 215 | dupes = 0 216 | for cell in celldict.keys(): 217 | if cell in request.session['cellset']: 218 | dupes += 1 219 | del celldict[cell] 220 | request.session['cellset'].add(cell) 221 | return render(request, template, {'bssid': cellid, 222 | 'hits': len(celldict), 223 | 'center': getCenter(celldict), 224 | 'bssids': celldict.keys(), 225 | 'apdict': celldict, 226 | 'manufdict': celldesc, 227 | 'LTE': LTE}) 228 | else: 229 | return render(request, 'wigle-wloc.html', {'ssid': '', 'center': (56.97518158, 24.17274475)}) 230 | 231 | 232 | def locateSSID(request, ssid=None): 233 | if ssid: 234 | access_point_wigle_results = wigle_query.get_location(SSID=ssid) 235 | num_results = len(access_point_wigle_results) 236 | 237 | if num_results == 0: 238 | return HttpResponse('0 Wigle results for SSID: {}'.format(ssid)) 239 | 240 | return render(request, 'wigle-wloc.html', 241 | {'ssid': ssid, 242 | 'hits': len(access_point_wigle_results), 243 | 'center': getCenter(access_point_wigle_results), 244 | 'bssids': access_point_wigle_results.keys(), 245 | 'apdict': access_point_wigle_results}) 246 | else: 247 | return render(request, 'wigle-wloc.html', 248 | {'ssid': '', 249 | 'center': (56.97518158, 24.17274475)}) 250 | 251 | 252 | def updateSSID(request): 253 | try: 254 | ssid = request.POST['ssid'] 255 | (lat, lon) = request.POST['position'].replace('(', '').replace(')', '').split(',') 256 | lat = float(lat) 257 | lon = float(lon) 258 | a = AP.objects.get(SSID=ssid) 259 | (a.lat, a.lon) = (lat, lon) 260 | a.save() 261 | return HttpResponse('Updated %s location to %s' % (a, (a.lat, a.lon))) 262 | except ObjectDoesNotExist: 263 | return HttpResponse('Not found in db.') 264 | -------------------------------------------------------------------------------- /security_ssid/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for security_ssid project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'security_ssid.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /tests/influxdb_test.py: -------------------------------------------------------------------------------- 1 | from db.influx import influxdb_client 2 | 3 | mobile_measurement_name = 'mobiledevices' 4 | access_pts_measurement_name = 'accesspoints' 5 | 6 | 7 | mobile_test_data_json = [ 8 | { 9 | "measurement": mobile_measurement_name, 10 | "tags": { 11 | "macaddress": "01:ab:03:cd", 12 | "sensor": "001", 13 | "location": "001" 14 | }, 15 | "fields": { 16 | "value": -55 17 | } 18 | } 19 | ] 20 | influxdb_client.write_points(mobile_test_data_json) 21 | 22 | # Yes, SQLi vuln introduced with string concatenation temporarily 23 | result = influxdb_client.query('select * from ' + mobile_measurement_name + ';') 24 | 25 | print("Mobile Result: {0}".format(result)) 26 | 27 | 28 | ap_test_data_json = [ 29 | { 30 | "measurement": access_pts_measurement_name, 31 | "tags": { 32 | "macaddress": "04:ef:06:gh", 33 | "sensor": "001", 34 | "location": "001" 35 | }, 36 | "fields": { 37 | "value": -56 38 | } 39 | } 40 | ] 41 | influxdb_client.write_points(ap_test_data_json) 42 | 43 | # Yes, SQLi vuln introduced with string concatenation temporarily 44 | result = influxdb_client.query('select * from ' + access_pts_measurement_name + ';') 45 | 46 | print("Access Points Result: {0}".format(result)) 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/wigle_test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | WIGLE_ENDPOINT_API_V2 = 'https://api.wigle.net/api/v2/network/search' 4 | 5 | class WigleSearch(): 6 | def __init__(self, user, password): 7 | self.user = user 8 | self.password = password 9 | 10 | def search(self, lat_range=None, long_range=None, variance=None, 11 | bssid=None, ssid=None, ssidlike=None, 12 | last_update=None, 13 | address=None, state=None, zipcode=None, 14 | on_new_page=None, max_results=100): 15 | """ 16 | Credit to: https://github.com/viraptor/wigle/blob/master/wigle/__init__.py 17 | Search the Wigle wifi database for matching entries. The following 18 | criteria are supported: 19 | Args: 20 | lat_range ((float, float)): latitude range 21 | long_range ((float, float)): longitude range 22 | variance (float): radius tolerance in degrees 23 | bssid (str): BSSID/MAC of AP 24 | ssid (str): SSID of network 25 | last_update (datetime): when was the AP last seen 26 | address (str): location, address 27 | state (str): location, state 28 | zipcode (str): location, zip code 29 | on_new_page (func(int)): callback to notify when requesting new 30 | page of results 31 | max_results (int): maximum number of results from search query 32 | Returns: 33 | [dict]: list of dicts describing matching wifis 34 | """ 35 | 36 | # onlymine=false&freenet=false&paynet=false&ssidlike=starbucks 37 | 38 | params = { 39 | 'latrange1': lat_range[0] if lat_range else "", 40 | 'latrange2': lat_range[1] if lat_range else "", 41 | 'longrange1': long_range[0] if long_range else "", 42 | 'longrange2': long_range[1] if long_range else "", 43 | 'variance': str(variance) if variance else "0.01", 44 | 'netid': bssid or "", 45 | 'ssid': ssid or "", 46 | 'ssidlike': ssidlike or "", 47 | # Filter points by how recently they've been updated, condensed date/time numeric string format 'yyyyMMddhhmmss' 48 | 'lastupdt': last_update.strftime("%Y%m%d%H%M%S") if last_update else "", 49 | 'onlymine': 'false', 50 | 'freenet': 'false', 51 | 'paynet': 'false', 52 | 'addresscode': address or "", 53 | 'statecode': state or "", 54 | 'zipcode': zipcode or "", 55 | } 56 | 57 | result_wifi = [] 58 | 59 | while True: 60 | if on_new_page: 61 | on_new_page(params.get('first', 1)) 62 | resp = requests.get(WIGLE_ENDPOINT_API_V2, auth=(self.user, self.password), params=params) 63 | data = resp.json() 64 | if not data['success']: 65 | raise_wigle_error(data) 66 | 67 | for result in data['results'][:max_results - len(result_wifi)]: 68 | fix_latlong_nums(result) 69 | result_wifi.append(result) 70 | 71 | if data['resultCount'] < 100 or len(result_wifi) >= max_results: 72 | break 73 | 74 | params['first'] = data['last'] + 1 75 | 76 | print(result_wifi) 77 | return result_wifi 78 | 79 | 80 | def fix_latlong_nums(net): 81 | net['trilat'] = float(net['trilat']) 82 | net['trilong'] = float(net['trilong']) 83 | 84 | 85 | def raise_wigle_error(data): 86 | message = data.get('message') 87 | if message == "too many queries": 88 | raise WigleRatelimitExceeded() 89 | else: 90 | raise WigleRequestFailure(message) 91 | 92 | 93 | class WigleError(Exception): 94 | pass 95 | 96 | 97 | class WigleRequestFailure(WigleError): 98 | pass 99 | 100 | 101 | class WigleRatelimitExceeded(WigleRequestFailure): 102 | pass 103 | 104 | --------------------------------------------------------------------------------