diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index a9241aa5..accec662 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
-github: billz
+github: RaspAP
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 505b7abf..de6bcee7 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,39 +7,53 @@ assignees: ''
---
-**Before submitting an issue**
-Please read this first https://github.com/billz/raspap-webgui/wiki/Reporting-issues
+
+
+
-**Actual behavior**
-Tell us what you observed instead.
+## Checklist
+
+- [ ] This is a bug report
+- [ ] I observed this bug on a clean install of the OS
+- [ ] I have followed the project prerequisites
+- [ ] I have searched this repository for existing issues
+- [ ] I checked the FAQ and official documentation before creating this issue
+- [ ] I have read and understand the issue reporting guidelines
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
+## Bug description
+
-**Additional context**
-Add any other context about the problem here.
+## Your environment
+1. Operating System: **ENTER HERE**
+2. Hardware and version:
+3. RaspAP version:
+4. Clean install of a compatible operating system?
+5. RaspAP Quick Install or Manual setup?
+6. Using default configuration?
+7. Simultaneous AP and managed mode?
+8. Onboard wireless chipset or external adapter?
+9. Other software or services running with RaspAP?
+
+## Steps to reproduce
+
+
+## Screenshots
+
+
+## Additional context
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..9a0287eb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+## Is your feature request related to a problem?
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+## Describe the solution you'd like
+A clear and concise description of what you want to happen.
+
+## Describe alternatives you've considered
+A clear and concise description of any alternative solutions or features you've considered.
+
+## Additional context
+Add any other context or screenshots about the feature request here.
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 00000000..17e3b052
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,18 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 60
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - pinned
+ - enhancement
+ - feature request
+# Label to use when marking an issue as stale
+staleLabel: stale
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..41e7dd98
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,67 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '17 9 * * 1'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'javascript', 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..14cbebc4
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,16 @@
+on:
+ issues:
+ types: [opened, edited]
+
+jobs:
+ auto_close_issues:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+ - name: Automatically close issues that don't follow the issue template
+ uses: lucasbento/auto-close-issues@v1.0.2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ issue-close-message: "@${issue.user.login}: hello! :wave:\n\nThis issue is being automatically closed because it does not follow the issue template.\nPlease review this project's issue policy https://docs.raspap.com/issues" # optional property
+ closed-issues-label: "invalid" # optional property
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..bef647c8
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,23 @@
+sudo: false
+language: php
+
+matrix:
+ fast_finish: true
+
+ include:
+ - php: '7.0'
+ - php: '7.1'
+ - php: '7.2'
+ - php: '7.3'
+ - php: '7.4'
+ - php: 'nightly'
+
+ allow_failures:
+ - php: nightly
+
+before_script:
+ - composer install --prefer-source --quiet --no-interaction
+
+# Run test script commands.
+script:
+ - composer test
diff --git a/BACKERS.md b/BACKERS.md
index 9e5c3ea3..a13b2e38 100644
--- a/BACKERS.md
+++ b/BACKERS.md
@@ -1,12 +1,51 @@
-# RaspAP Sponors
+
-Development of RaspAP is made possible thanks to our awesome sponsors!
-You can join them by [becoming a sponsor](https://github.com/sponsors/billz).
+Development of RaspAP is made possible thanks to a sponsorware release model. This means that new features are first exclusively released to sponsors as part of **Insiders**. Read on to learn how sponsorship works, and how easy it is to get access to Insiders.
-### 💖 Benefactors
+## How sponsorship works
+New features first land in **Insiders**, which means that *sponsors will have access to them immediately*. Every feature is tied to a funding goal in monthly subscriptions. When a funding goal is hit, the features that are tied to it are merged back into the [public RaspAP repository](https://github.com/RaspAP/raspap-webgui) and released for general availability. Bugfixes and minor enhancements are always released simultaneously in both editions.
-### 🏆 Gilded supporters
+Don't want to sponsor? No problem, RaspAP already has tons of features available, so chances are that most of your requirements are already satisfied. See the list of **exclusive features** to learn which features are currently only available to sponsors.
-### 🤖 Robot fuelers
+## How to become a sponsor
+You can become a sponsor using your individual or organization's GitHub account. Just pick any tier from $10/month and complete the checkout. Then, after a few hours, you will be added as a team member to the super-secret private GitHub repository containing the Insiders edition, which has all exclusive features. In addition, you get access to Insiders-only team discussions and content.
-### ☕️ Coffee supporters
+**Important**: If you're sponsoring [RaspAP](https://github.com/RaspAP/sponsors) through a GitHub organization, please send a short email to [sponsors@raspap.com](mailto:sponsors@raspap.com) with the name of your organization and the account that should be added as a collaborator.
+
+## Exclusive features
+The following features are currently available exclusively to sponsors. A tangible side benefit of sponsorship is that Insiders are able to help steer future development of RaspAP. This is done through Insiders' access to discussions, feature requests, issues and pull requests in the private GitHub repository.
+
+ ✅ [Network device management](https://docs.raspap.com/net-devices/)
+ ✅ [Firewall settings](https://docs.raspap.com/firewall/)
+ ✅ [WPA3-Personal AP security](https://docs.raspap.com/ap-basics/#wpa3-personal)
+ ✅ [802.11w Protected Management Frames](https://docs.raspap.com/ap-basics/#80211w)
+ ✅ [Printable Wi-Fi signs](https://docs.raspap.com/ap-basics/#printable-signs)
+ ✅ [Drag & drop dashboard widgets](https://docs.raspap.com/ap-basics/#drag-drop-widgets)
+ ✅ [MAC address cloning](https://docs.raspap.com/net-devices/#changing-the-mac-address)
+ ✅ [Network diagnostics](https://docs.raspap.com/net-devices/#diagnostics)
+ ✅ [WireGuard VPN kill switch](https://docs.raspap.com/wireguard/#kill-switch)
+ ⚙️ Dynamic DNS support (in progress)
+
+Look for the list above to grow as we add more exclusive features. Be sure to visit this page from time to time to learn about what's new, check the [Insiders docs page](https://docs.raspap.com/insiders/) and follow [@RaspAP on Twitter](https://twitter.com/rasp_ap) to stay updated.
+
+## Funding targets
+Below is a list of funding targets. When a funding target is reached, the features that are tied to it are merged back into RaspAP and released to the public for general availability.
+
+### $1000
+The second **Insiders Edition** includes the features listed above.
+
+### $500
+The [first Insiders Edition goal](https://docs.raspap.com/insiders/#500-1st-insiders-edition) was reached in December 2021. Thank you sponsors!
+
+## Quarterly giving
+Beginning in 2022, each quarter 15% of all proceeds from Insiders will be donated directly to the [Raspberry Pi Foundation](https://www.raspberrypi.org/). The Raspberry Pi Foundation is a UK-based charity that works to put the power of computing and digital making into the hands of people all over the world.
+
+[![Get involved with the Raspberry Pi Foundation](https://img.youtube.com/vi/dEzg92g1LHw/0.jpg)](https://www.youtube.com/watch?v=dEzg92g1LHw)
+
+When you become an Insider, not only do you support development of RaspAP but you also help inspire young people by harnessing the power of computing to solve problems and express themselves creatively.
+
+## Frequently asked questions
+We've covered all you need to know [here](https://docs.raspap.com/insiders/#frequently-asked-questions).
+
+### Terms
+See our [official project documentation](https://docs.raspap.com/insiders/#terms).
diff --git a/CNAME b/CNAME
deleted file mode 100644
index 0949dd03..00000000
--- a/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-raspap.com
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a49d3714..e35dea31 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,10 +1,13 @@
## How to contribute
-1. File an issue in the repository, using the bug tracker, describing the
- contribution you'd like to make. This will help us to get you started on the
- right foot.
-2. Fork the project in your account and create a new branch:
- `your-great-feature`.
-3. Commit your changes in that branch.
-4. Open a pull request, and reference the initial issue in the pull request
- message.
+1. Fork the project in your account and create a new branch: `your-great-feature`.
+2. Open an issue in the repository describing the feature contribution you'd like to make. This will help us get you started on the right foot.
+3. Commit changes in your feature branch.
+4. Open a pull request and reference the initial issue in the pull request message.
+
+### Coding standards
+This project follows the [PSR-2](http://www.php-fig.org/psr/psr-2/) coding style guidelines. There are many ways to check your code for PSR-2. An excellent tool is [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). The command line tool phpcs can be run against any single file. [Phing](https://www.phing.info/), a PHP build tool, integrates nicely with `phpcs` to automate PSR-2 checks across all source files in a project.
+
+### Development process
+Development processes used by contributors to the project are described [on this page](https://docs.raspap.com/developers/). It does not endorse one over the other; rather it is meant to share two different approaches.
+
diff --git a/README.md b/README.md
index cd1668b4..dc7f2c99 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
![](https://i.imgur.com/xeKD93p.png)
-# `$raspap` [![Release 2.1](https://img.shields.io/badge/Release-2.1-green.svg)](https://github.com/billz/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Sponsor](https://img.shields.io/badge/sponsor-%F0%9F%92%96-green)](https://github.com/sponsors/billz)
+[![Release 2.9.0](https://img.shields.io/badge/release-v2.9.0-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Join%20Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/RaspAP?style=social)](https://www.reddit.com/r/RaspAP/)
-A simple, responsive web interface to control wifi, hostapd and related services on the Raspberry Pi.
+RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our popular [Quick installer](#quick-installer) creates a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, WireGuard and OpenVPN support, [SSL certificates](https://docs.raspap.com/ssl-quick/), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
-This project was inspired by a [blog post](http://sirlagz.net/2013/02/06/script-web-configuration-page-for-raspberry-pi/) by SirLagz about using a web page rather than ssh to configure wifi and hostapd settings on the Raspberry Pi. I began by prettifying the UI by wrapping it in [SB Admin 2](https://github.com/BlackrockDigital/startbootstrap-sb-admin-2), a Bootstrap based admin theme. Since then, the project has evolved to include greater control over many aspects of a networked RPi, better security, authentication, a Quick Installer, support for OpenVPN, themes and more. RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects.
+RaspAP has been featured on sites such as [Instructables](http://www.instructables.com/id/Raspberry-Pi-As-Completely-Wireless-Router/), [Adafruit](https://blog.adafruit.com/2016/06/24/raspap-wifi-configuration-portal-piday-raspberrypi-raspberry_pi/), [Raspberry Pi Weekly](https://www.raspberrypi.org/weekly/commander/) and [Awesome Raspberry Pi](https://project-awesome.org/thibmaek/awesome-raspberry-pi) and implemented in countless projects.
-We'd be curious to hear about how you use this with [your own RPi-powered projects](https://github.com/billz/raspap-awesome). Until then, here are some screenshots:
+We hope you enjoy using RaspAP as much as we do creating it. Tell us how you use this with [your own projects](https://github.com/raspap/raspap-awesome).
-![](https://i.imgur.com/fwekyGE.gif)
+![](https://i.imgur.com/uhBFoOB.png)
![](https://i.imgur.com/EiIpdOS.gif)
![](https://i.imgur.com/eCjUS1H.gif)
![](https://i.imgur.com/5FT2BcS.gif)
@@ -16,211 +16,129 @@ We'd be curious to hear about how you use this with [your own RPi-powered projec
- [Prerequisites](#prerequisites)
- [Quick installer](#quick-installer)
+ - [Join Insiders](#join-insiders)
+ - [WireGuard support](#wireguard-support)
+ - [OpenVPN support](#openvpn-support)
+ - [Ad Blocking](#ad-blocking)
+ - [Bridged AP](#bridged-ap)
- [Simultaneous AP and Wifi client](#simultaneous-ap-and-wifi-client)
- - [Support us](#support-us)
- [Manual installation](#manual-installation)
+ - [802.11ac 5GHz support](#80211ac-5ghz-support)
+ - [Supported operating systems](#supported-operating-systems)
- [Multilingual support](#multilingual-support)
- [HTTPS support](#https-support)
- - [OpenVPN support](#openvpn-support)
- [How to contribute](#how-to-contribute)
- [Reporting issues](#reporting-issues)
- [License](#license)
## Prerequisites
-Start with a clean install of the [latest release of Raspbian](https://www.raspberrypi.org/downloads/raspbian/) (currently Buster). Raspbian Buster Lite is recommended.
+Start with a clean install of the [latest release of Raspberry Pi OS (32-bit) Lite](https://www.raspberrypi.org/software/operating-systems/#raspberry-pi-os-32-bit). The Raspberry Pi OS desktop and 64-bit beta distros are unsupported.
1. Update Raspbian, including the kernel and firmware, followed by a reboot:
```
sudo apt-get update
-sudo apt-get dist-upgrade
+sudo apt-get full-upgrade
sudo reboot
```
-2. Set the WiFi country in raspi-config's **Localisation Options**: `sudo raspi-config`
+2. Set the "WLAN country" option in `raspi-config`'s **Localisation Options**: `sudo raspi-config`
-3. If you have an older Raspberry Pi without an onboard WiFi chipset, the [**Edimax Wireless 802.11b/g/n nano USB adapter**](https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_n150/ew-7811un) is an excellent option – it's small, cheap and has good driver support.
+3. If you have a device without an onboard wireless chipset, the [**Edimax Wireless 802.11b/g/n nano USB adapter**](https://www.edimax.com/edimax/merchandise/merchandise_detail/data/edimax/global/wireless_adapters_n150/ew-7811un) is an excellent option – it's small, cheap and has good driver support.
With the prerequisites done, you can proceed with either the Quick installer or Manual installation steps below.
## Quick installer
-Install RaspAP from your RaspberryPi's shell prompt:
+Install RaspAP from your device's shell prompt:
```sh
curl -sL https://install.raspap.com | bash
```
-The [installer](https://github.com/billz/raspap-webgui/wiki/Quick-Installer-usage) will complete the steps in the manual installation (below) for you.
+The [installer](https://docs.raspap.com/quick/) will complete the steps in the manual installation (below) for you.
After the reboot at the end of the installation the wireless network will be
configured as an access point as follows:
* IP address: 10.3.141.1
* Username: admin
* Password: secret
-* DHCP range: 10.3.141.50 to 10.3.141.255
+* DHCP range: 10.3.141.50 — 10.3.141.254
* SSID: `raspi-webgui`
* Password: ChangeMe
-**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your RPi. Best results are obtained by connecting an RPi to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://github.com/billz/raspap-webgui/wiki/FAQs#how-do-i-prepare-the-sd-card-to-connect-to-wifi-in-headless-mode). Please [read this](https://github.com/billz/raspap-webgui/wiki/Reporting-issues) before reporting an issue.
+**Note:** As the name suggests, the Quick Installer is a great way to quickly setup a new AP. However, it does not automagically detect the unique configuration of your system. Best results are obtained by connecting to ethernet (`eth0`) or as a WiFi client, also known as managed mode, with `wlan0`. For the latter, refer to [this FAQ](https://docs.raspap.com/faq/#headless). Special instructions for the Pi Zero W are [available here](https://docs.raspap.com/ap-sta/).
+
+Please [read this](https://docs.raspap.com/issues/) before reporting an issue.
+
+## Join Insiders
+[![](https://i.imgur.com/eml7k0b.png)](https://github.com/sponsors/RaspAP/)
+
+RaspAP is free software, but powered by _your_ support. If you find RaspAP useful for your personal or commercial projects, [become an Insider](https://github.com/sponsors/RaspAP/) and get early access to [exclusive features](https://docs.raspap.com/insiders/#exclusive-features) in the [Insiders Edition](https://docs.raspap.com/insiders/).
+
+A tangible side benefit of sponsorship is that **Insiders** are able to help _steer future development of RaspAP_. This is done through Insiders' team access to discussions, feature requests, issues and more in the private GitHub repository.
+
+## WireGuard support
+
+![](https://i.imgur.com/5YDv37e.png)
+
+WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be considerably more performant than OpenVPN, and is generally regarded as the most secure, easiest to use, and simplest VPN solution for modern Linux distributions.
+
+WireGuard may be optionally installed by the [Quick Installer](https://docs.raspap.com/quick/). Once this is done, you can manage local (server) settings, create a peer configuration and control the `wg-quick` service with RaspAP.
+
+Details are [provided here](https://docs.raspap.com/wireguard/).
+
+## OpenVPN support
+
+![](https://i.imgur.com/ta7tCon.png)
+
+OpenVPN may be optionally installed by the Quick Installer. Once this is done, you can [manage client configurations](https://docs.raspap.com/openvpn/) and the `openvpn-client` service with RaspAP.
+
+To configure an OpenVPN client, upload a valid .ovpn file and, optionally, specify your login credentials. RaspAP will store your client configuration and add firewall rules to forward traffic from OpenVPN's `tun0` interface to your configured wireless interface.
+
+See our [OpenVPN documentation](https://docs.raspap.com/openvpn/) for more information.
+
+
+## Ad Blocking
+This feature uses DNS blacklisting to block requests for ads, trackers and other undesirable hosts. To enable ad blocking, simply respond to the prompt during the installation. As a beta release, we encourage testing and feedback from users of RaspAP.
+
+Details are [provided here](https://docs.raspap.com/adblock/).
+
+## Bridged AP
+By default RaspAP configures a routed AP for your clients to connect to. A bridged AP configuration is also possible. Slide the **Bridged AP mode** toggle under the **Advanced** tab of **Configure hotspot**, then save and restart the hotspot.
+
+**Note:** In bridged mode, all routing capabilities are handled by your upstream router. Because your router assigns IP addresses to your device's hotspot and its clients, you might not be able to reach the RaspAP web interface from the default `10.3.141.1` address. Instead use your RPi's hostname followed by `.local` to access the RaspAP web interface. With Raspbian default settings, this should look like `raspberrypi.local`. Alternate methods are [discussed here](https://www.raspberrypi.org/documentation/remote-access/ip-address.md).
+
+More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/).
## Simultaneous AP and Wifi client
-RaspAP lets you easily create an AP with a Wifi client configuration. With your RPi configured in managed mode, enable the AP from the **Advanced** tab of **Configure hotspot** by sliding the **Wifi client AP mode** toggle. Save settings and start the hotspot. The managed mode AP is functional without restart.
+RaspAP lets you create an AP with a Wifi client configuration, often called [AP-STA mode](https://docs.raspap.com/ap-sta/). With your system configured in managed mode, enable the AP from the **Advanced** tab of **Configure hotspot** by sliding the **Wifi client AP mode** toggle. Save settings and start the hotspot. The managed mode AP is functional without restart.
-![](https://i.imgur.com/YObvd32.gif)
-
-**Note:** This option is disabled until you configure your RPi as a wireless client. For a Raspberry Pi operating in [managed mode](https://github.com/billz/raspap-webgui/wiki/FAQs#how-do-i-prepare-the-sd-card-to-connect-to-wifi-in-headless-mode) without an `eth0` connection, this configuration must be enabled _before_ a reboot.
-
-## Support us
-
-RaspAP is free software, but powered by your support. If you find RaspAP useful for your personal or commercial projects, please [become a sponsor](https://github.com/sponsors/billz) or make a one-time donation with [Beerpay](https://beerpay.io/billz/raspap-webgui). Either option makes a big difference!
-
-[![Beerpay](https://beerpay.io/billz/raspap-webgui/badge.svg)](https://beerpay.io/billz/raspap-webgui)
+**Note:** This option is disabled until you configure your system as a wireless client. For a device operating in [managed mode](https://docs.raspap.com/faq/#headless) without an `eth0` connection, this configuration must be enabled [_before_ a reboot](https://docs.raspap.com/ap-sta/).
## Manual installation
-These steps apply to the latest release of Raspbian (currently [Buster](https://www.raspberrypi.org/downloads/raspbian/)). Notes for previously released versions are provided, where applicable. Start off by installing git, lighttpd, php7, hostapd and dnsmasq.
-```sh
-sudo apt-get install git lighttpd php7.1-cgi hostapd dnsmasq vnstat
-```
-**Note:** for Raspbian Stretch, replace `php7.1-cgi` with `php7.0-cgi`. For Raspbian Jessie and older versions, use `php5-cgi`. After that, enable PHP for lighttpd and restart it for the settings to take effect.
-```sh
-sudo lighttpd-enable-mod fastcgi-php
-sudo service lighttpd restart
-```
-Now comes the fun part. For security reasons, the `www-data` user which lighttpd runs under is not allowed to start or stop daemons, or run commands like ifdown and ifup, all of which we want our page to do.
-So what I have done is added the `www-data` user to the sudoers file, but with restrictions on what commands the user can run. Add the following to the end of `/etc/sudoers`:
+Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/).
-```sh
-www-data ALL=(ALL) NOPASSWD:/sbin/ifdown
-www-data ALL=(ALL) NOPASSWD:/sbin/ifup
-www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wpa_supplicant/wpa_supplicant.conf
-www-data ALL=(ALL) NOPASSWD:/bin/cat /etc/wpa_supplicant/wpa_supplicant-wlan[0-9].conf
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant.conf
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wifidata /etc/wpa_supplicant/wpa_supplicant-wlan[0-9].conf
-www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] scan_results
-www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] scan
-www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] reconfigure
-www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i wlan[0-9] select_network
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/hostapddata /etc/hostapd/hostapd.conf
-www-data ALL=(ALL) NOPASSWD:/bin/systemctl start hostapd.service
-www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop hostapd.service
-www-data ALL=(ALL) NOPASSWD:/bin/systemctl start dnsmasq.service
-www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop dnsmasq.service
-www-data ALL=(ALL) NOPASSWD:/bin/systemctl start openvpn-client@client
-www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop openvpn-client@client
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/openvpn.ovpn /etc/openvpn/client/client.conf
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/authdata /etc/openvpn/client/login.conf
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dnsmasqdata /etc/dnsmasq.conf
-www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/dhcpddata /etc/dhcpcd.conf
-www-data ALL=(ALL) NOPASSWD:/sbin/shutdown -h now
-www-data ALL=(ALL) NOPASSWD:/sbin/reboot
-www-data ALL=(ALL) NOPASSWD:/sbin/ip link set wlan[0-9] down
-www-data ALL=(ALL) NOPASSWD:/sbin/ip link set wlan[0-9] up
-www-data ALL=(ALL) NOPASSWD:/sbin/ip -s a f label wlan[0-9]
-www-data ALL=(ALL) NOPASSWD:/bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf
-www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/enablelog.sh
-www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/disablelog.sh
-www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/servicestart.sh
-www-data ALL=(ALL) NOPASSWD:/etc/raspap/lighttpd/configport.sh
-www-data ALL=(ALL) NOPASSWD:/etc/raspap/openvpn/configauth.sh
-```
+## 802.11ac 5GHz support
+RaspAP provides an 802.11ac wireless mode option for supported hardware (currently the RPi 3B+/4 and compatible Orange Pi models) and wireless regulatory domains. See [this FAQ](https://docs.raspap.com/faq/#80211ac) for more information.
-Once those modifications are done, git clone the files to `/var/www/html`.
-**Note:** for older versions of Raspbian (before Jessie, May 2016) use
-`/var/www` instead.
-```sh
-sudo rm -rf /var/www/html
-sudo git clone https://github.com/billz/raspap-webgui /var/www/html
-```
-Move the high-res favicons to the web root.
-```
-sudo mv /var/www/html/app/icons/* /var/www/html
-```
-Set the files ownership to `www-data` user.
-```sh
-sudo chown -R www-data:www-data /var/www/html
-```
-Move the RaspAP configuration file to the correct location.
-```sh
-sudo mkdir /etc/raspap
-sudo mv /var/www/html/raspap.php /etc/raspap/
-sudo chown -R www-data:www-data /etc/raspap
-```
-Move the HostAPD logging and service control shell scripts to the correct location.
-```sh
-sudo mkdir /etc/raspap/hostapd
-sudo mv /var/www/html/installers/*log.sh /etc/raspap/hostapd
-sudo mv /var/www/html/installers/service*.sh /etc/raspap/hostapd
-```
-Set ownership and permissions for logging and service control scripts.
-```sh
-sudo chown -c root:www-data /etc/raspap/hostapd/*.sh
-sudo chmod 750 /etc/raspap/hostapd/*.sh
-```
-Add the following lines to `/etc/rc.local` before `exit 0`.
-```sh
-echo 1 > /proc/sys/net/ipv4/ip_forward #RASPAP
-iptables -t nat -A POSTROUTING -j MASQUERADE #RASPAP
-iptables -t nat -A POSTROUTING -s 192.168.50.0/24 ! -d 192.168.50.0/24 -j MASQUERADE #RASPAP
-```
-Force a reload of new settings in `/etc/rc.local`.
-```sh
-sudo systemctl restart rc-local.service
-sudo systemctl daemon-reload
-```
-Unmask and enable the hostapd service.
-```sh
-sudo systemctl unmask hostapd.service
-sudo systemctl enable hostapd.service
-```
-Move the raspap service to the correct location and enable it.
-```
-sudo mv /var/www/html/installers/raspap.service /lib/systemd/system
-sudo systemctl enable raspap.service
-```
-Copy the configuration files for dhcpcd, dnsmasq, and hostapd.
-```
-sudo mv /var/www/html/config/default_hostapd /etc/default/hostapd
-sudo mv /var/www/html/config/hostapd.conf /etc/hostapd/hostapd.conf
-sudo mv /var/www/html/config/dnsmasq.conf /etc/dnsmasq.conf
-sudo mv /var/www/html/config/dhcpcd.conf /etc/dhcpcd.conf
-sudo mv /var/www/html/config/config.php /var/www/html/includes/
-```
-(Optional) Optimize PHP
-```
-sudo sed -i -E 's/^session\.cookie_httponly\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/session.cookie_httponly = 1/' /etc/php/7.1/cgi/php.ini
-sudo sed -i -E 's/^;?opcache\.enable\s*=\s*(0|([O|o]ff)|([F|f]alse)|([N|n]o))\s*$/opcache.enable = 1/' /etc/php/7.1/cgi/php.ini
-sudo phpenmod opcache
-```
-Reboot and it should be up and running!
-```sh
-sudo reboot
-```
+## Supported operating systems
+RaspAP was originally made for Raspbian, but now also installs on the following Debian-based distros.
-The default username is 'admin' and the default password is 'secret'.
+| Distribution | Release | Architecture | Support |
+|---|:---:|:---:|:---:|
+| Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official |
+| Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official |
+| Armbian | Bullseye | [ARM](https://docs.armbian.com/#supported-socs) | Official |
+| Debian | Bullseye | ARM / x86_64 | Beta |
+| Ubuntu | 18.04 LTS / 19.10 | ARM / x86_64 | Beta |
+
+![](https://i.imgur.com/luiyYNw.png)
+
+We find Armbian particularly well-suited for this project. Please note that "supported" is not a guarantee. If you are able to improve support for your preferred distro, we encourage you to [actively contribute](#how-to-contribute) to the project.
## Multilingual support
-RaspAP uses [GNU Gettext](https://www.gnu.org/software/gettext/) to manage multilingual messages. In order to use RaspAP with one of our supported translations, you must configure a corresponding language package on your RPi. To list languages currently installed on your system, use `locale -a` at the shell prompt. To generate new locales, run `sudo dpkg-reconfigure locales` and select any other desired locales. Details are provided on our [wiki](https://github.com/billz/raspap-webgui/wiki/Translations#raspap-in-your-language).
+RaspAP uses [GNU Gettext](https://www.gnu.org/software/gettext/) to manage multilingual messages. In order to use RaspAP with one of our supported translations, you must configure a corresponding language package on your RPi. To list languages currently installed on your system, use `locale -a` at the shell prompt. To generate new locales, run `sudo dpkg-reconfigure locales` and select any other desired locales. Details are provided on our [documentation site](https://docs.raspap.com/translations/).
-The following translations are currently maintained by the project:
-
-- Deutsch
-- Français
-- Italiano
-- Português
-- Svenska
-- Nederlands
-- 简体中文 (Chinese Simplified)
-- Indonesian
-- 한국어 (Korean)
-- 日本語 (Japanese)
-- Tiếng Việt (Vietnamese)
-- Čeština
-- Русский
-- Español
-- Finnish
-- Sinhala
-- Türkçe
-
-If your language is not in the list above, why not [contribute a translation](https://github.com/billz/raspap-webgui/wiki/Translations#contributing-a-translation)? Contributors will receive credit as the original translators.
+See this list of [supported languages](https://docs.raspap.com/translations/#supported-languages) that are actively maintained by volunteer translators. If your language is not supported, why not [contribute a translation](https://docs.raspap.com/translations/#contributing-to-a-translation)? Contributors will receive credit as the original translators.
## HTTPS support
-The Quick Installer may be used to [generate SSL certificates](https://github.com/billz/raspap-webgui/wiki/SSL-certificates-(Quick-Installer)) with `mkcert`. The installer automates the manual steps [described in the wiki](https://github.com/billz/raspap-webgui/wiki/SSL-(Manual-steps)), including configuring lighttpd with SSL support.
+The Quick Installer may be used to [generate SSL certificates](https://docs.raspap.com/ssl-quick/) with `mkcert`. The installer automates the manual steps [described here](https://docs.raspap.com/ssl-manual/), including configuring lighttpd with SSL support.
Simply append the `-c` or `--cert` option to the Quick Installer, like so:
@@ -230,27 +148,30 @@ curl -sL https://install.raspap.com | bash -s -- --cert
**Note**: this only installs mkcert and generates an SSL certificate with the input you provide. It does *not* (re)install RaspAP.
-More information on SSL certificates and HTTPS support is available [on our wiki](https://github.com/billz/raspap-webgui/wiki/SSL-certificates-(Quick-Installer)).
-
-## OpenVPN support
-OpenVPN may be optionally installed by the Quick Installer. Once this is done, you can managage a client configuration and the `openvpn-client` service with RaspAP.
-
-To configure an OpenVPN client, upload a valid .ovpn file and, optionally, specify your login credentials. RaspAP will store your client configuration and add firewall rules to forward traffic from OpenVPN's `tun0` interface to your configured wireless interface.
-
-**Note**: this feature is currently in beta. Please [read this](https://github.com/billz/raspap-webgui/wiki/FAQs#-openvpn-fails-to-start-andor-i-have-no-internet-help) before reporting an issue.
+More information on SSL certificates and HTTPS support is available [in our documentation](https://docs.raspap.com/ssl-quick/).
## How to contribute
+1. Fork the project in your account and create a new branch: `your-great-feature`.
+2. Open an issue in the repository describing the feature contribution you'd like to make.
+3. Commit changes in your feature branch.
+4. Open a pull request and reference the initial issue in the pull request message.
-1. File an issue in the repository describing the contribution you'd like to make. This will help us get you started on the
- right foot.
-2. Fork the project in your account and create a new branch: `your-great-feature`.
-3. Commit your changes in that branch.
-4. Open a pull request, and reference the initial issue in the pull request message.
-
-This project follows the [PSR-2](http://www.php-fig.org/psr/psr-2/) coding style guidelines. There are many ways to check your code for PSR-2. An excellent tool is [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). The command line tool `phpcs` can be run against any single file. [Phing](https://www.phing.info/), a PHP build tool, integrates nicely with `phpcs` to automate PSR-2 checks across all source files in a project.
+Find out more about our [coding style guidelines and recommended tools](CONTRIBUTING.md).
## Reporting issues
-Please [read this](https://github.com/billz/raspap-webgui/wiki/Reporting-issues) before reporting a bug.
+Please [read this](https://docs.raspap.com/issues/) before reporting a bug.
+
+## Contributors
+
+### Code Contributors
+This project exists thanks to all the awesome people who [contribute](CONTRIBUTING.md) their time and expertise.
+
+
+
+### Financial Contributors
+Development of RaspAP is made possible thanks to a sponsorware release model. This means that new features are first exclusively released to sponsors as part of [**Insiders**](https://github.com/sponsors/RaspAP).
+
+Learn more about [how sponsorship works](https://docs.raspap.com/insiders/#how-sponsorship-works), and how easy it is to get access to Insiders.
## License
See the [LICENSE](./LICENSE) file.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..b37c905e
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,27 @@
+# Security Policy
+
+The RaspAP team and community take all security vulnerabilities seriously. This document outlines security procedures and general policies for the RaspAP open source projects as found on https://github.com/RaspAP/.
+If you believe you have found a security vulnerability in any RaspAP-owned repository, please report it to us as described below.
+
+## Reporting a vulnerability in RaspAP
+
+Thank you for improving the security of our open source software.
+We appreciate your efforts and responsible disclosure, and will make every effort to acknowledge your contributions.
+
+Please report (suspected) security vulnerabilities to [security@raspap.com](mailto:security@raspap.com). The requested information listed below will help us better understand the nature and scope of the possible issue:
+
+1. Type of issue (eg. shell exploit, cross-site scripting, etc.)
+2. Full paths of source file(s) related to the manifestation of the issue
+3. The location of the affected source code (tag/branch/commit or direct URL)
+4. Any special configuration required to reproduce the issue
+5. Step-by-step instructions to reproduce the issue
+6. Proof-of-concept or exploit code (if possible)
+7. Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+You will receive a response from us within 48 hours. Developers may ask for additional information or clarity on your report.
+If the issue is confirmed, we will release a patch as soon as possible depending on complexity, but historically within a few days.
+
+## Third-party modules
+Report security vulnerabilities in third-party modules to the person or team maintaining the module.
diff --git a/ajax/adblock/update_blocklist.php b/ajax/adblock/update_blocklist.php
new file mode 100644
index 00000000..86f326b4
--- /dev/null
+++ b/ajax/adblock/update_blocklist.php
@@ -0,0 +1,24 @@
+$return];
+ echo json_encode($jsonData);
+}
+
diff --git a/ajax/bandwidth/get_bandwidth.php b/ajax/bandwidth/get_bandwidth.php
index 37d4adc9..4fcf8057 100644
--- a/ajax/bandwidth/get_bandwidth.php
+++ b/ajax/bandwidth/get_bandwidth.php
@@ -1,6 +1,6 @@
IFNAMSIZ) {
require_once './get_bandwidth_hourly.php';
-exec(sprintf('vnstat -i %s --json ', escapeshellarg($interface)), $jsonstdoutvnstat,
- $exitcodedaily);
+exec(
+ sprintf('vnstat -i %s --json ', escapeshellarg($interface)), $jsonstdoutvnstat,
+ $exitcodedaily
+);
if ($exitcodedaily !== 0) {
- exit('vnstat error');
+ exit('vnstat error');
}
$jsonobj = json_decode($jsonstdoutvnstat[0], true);
$timeunits = filter_input(INPUT_GET, 'tu');
if ($timeunits === 'm') {
// months
- $jsonData = $jsonobj['interfaces'][0]['traffic']['months'];
+ $jsonData = $jsonobj['interfaces'][0]['traffic']['month'];
} else {
// default: days
- $jsonData = $jsonobj['interfaces'][0]['traffic']['days'];
+ $jsonData = $jsonobj['interfaces'][0]['traffic']['day'];
}
$datasizeunits = filter_input(INPUT_GET, 'dsu');
+$dsu_factor = $datasizeunits == "mb" ? 1024 * 1024 : 1024;
header('X-Content-Type-Options: nosniff');
header('Content-Type: application/json');
echo '[ ';
$firstelm = true;
for ($i = count($jsonData) - 1; $i >= 0; --$i) {
if ($timeunits === 'm') {
- $dt = DateTime::createFromFormat('Y n', $jsonData[$i]['date']['year'].' '.
- $jsonData[$i]['date']['month']);
+ $dt = DateTime::createFromFormat(
+ 'Y n', $jsonData[$i]['date']['year'].' '.
+ $jsonData[$i]['date']['month']
+ );
} else {
- $dt = DateTime::createFromFormat('Y n j', $jsonData[$i]['date']['year'].' '.
+ $dt = DateTime::createFromFormat(
+ 'Y n j', $jsonData[$i]['date']['year'].' '.
$jsonData[$i]['date']['month'].' '.
- $jsonData[$i]['date']['day']);
+ $jsonData[$i]['date']['day']
+ );
}
if ($firstelm) {
@@ -67,13 +74,8 @@ for ($i = count($jsonData) - 1; $i >= 0; --$i) {
echo ',';
}
- if ($datasizeunits == 'mb') {
- $datasend = round($jsonData[$i]['tx'] / 1024, 0);
- $datareceived = round($jsonData[$i]['rx'] / 1024, 0);
- } else {
- $datasend = $jsonData[$i]['rx'];
- $datareceived = $jsonData[$i]['rx'];
- }
+ $datasend = round($jsonData[$i]['tx'] / $dsu_factor, 0);
+ $datareceived = round($jsonData[$i]['rx'] / $dsu_factor, 0);
if ($timeunits === 'm') {
echo '{ "date": "' , $dt->format('Y-m') , '", "rx": "' , $datareceived ,
diff --git a/ajax/bandwidth/get_bandwidth_hourly.php b/ajax/bandwidth/get_bandwidth_hourly.php
index 482b6c6f..5e2f93f1 100644
--- a/ajax/bandwidth/get_bandwidth_hourly.php
+++ b/ajax/bandwidth/get_bandwidth_hourly.php
@@ -1,66 +1,66 @@
- array('date' => '00:00', 'rx' => 0, 'tx' => 0),
- 1 => array('date' => '01:00', 'rx' => 0, 'tx' => 0),
- 2 => array('date' => '02:00', 'rx' => 0, 'tx' => 0),
- 3 => array('date' => '03:00', 'rx' => 0, 'tx' => 0),
- 4 => array('date' => '04:00', 'rx' => 0, 'tx' => 0),
- 5 => array('date' => '05:00', 'rx' => 0, 'tx' => 0),
- 6 => array('date' => '06:00', 'rx' => 0, 'tx' => 0),
- 7 => array('date' => '07:00', 'rx' => 0, 'tx' => 0),
- 8 => array('date' => '08:00', 'rx' => 0, 'tx' => 0),
- 9 => array('date' => '09:00', 'rx' => 0, 'tx' => 0),
- 10 => array('date' => '10:00', 'rx' => 0, 'tx' => 0),
- 11 => array('date' => '11:00', 'rx' => 0, 'tx' => 0),
- 12 => array('date' => '12:00', 'rx' => 0, 'tx' => 0),
- 13 => array('date' => '13:00', 'rx' => 0, 'tx' => 0),
- 14 => array('date' => '14:00', 'rx' => 0, 'tx' => 0),
- 15 => array('date' => '15:00', 'rx' => 0, 'tx' => 0),
- 16 => array('date' => '16:00', 'rx' => 0, 'tx' => 0),
- 17 => array('date' => '17:00', 'rx' => 0, 'tx' => 0),
- 18 => array('date' => '18:00', 'rx' => 0, 'tx' => 0),
- 19 => array('date' => '19:00', 'rx' => 0, 'tx' => 0),
- 20 => array('date' => '20:00', 'rx' => 0, 'tx' => 0),
- 21 => array('date' => '21:00', 'rx' => 0, 'tx' => 0),
- 22 => array('date' => '22:00', 'rx' => 0, 'tx' => 0),
- 23 => array('date' => '23:00', 'rx' => 0, 'tx' => 0)
- );
-
-
-
-
- exec(sprintf('vnstat -i %s --json h', escapeshellarg($interface)), $jsonstdoutvnstat, $exitcodedaily);
- if ($exitcodedaily !== 0) {
- exit('vnstat error');
- }
-
- $jsonobj = json_decode($jsonstdoutvnstat[0], true)['interfaces'][0];
- $jsonData = $jsonobj['traffic']['hours'];
- for ($i = count($jsonData) - 1; $i >= 0; --$i) {
- $data_template[$jsonData[$i]['id']]['rx'] = round($jsonData[$i]['rx'] / 1024, 0);
- $data_template[$jsonData[$i]['id']]['tx'] = round($jsonData[$i]['tx'] / 1024, 0);
- }
-
- $data = array();
- $hour = $jsonobj['updated']['time']['hour'];
- foreach ($data_template as $key => $value) {
- if ($key > $hour) {
- array_push($data, $value);
- }
- }
- foreach ($data_template as $key => $value) {
- if ($key <= $hour) {
- array_push($data, $value);
- }
- }
- echo json_encode($data);
- exit(0);
-}
+ array('date' => '00:00', 'rx' => 0, 'tx' => 0),
+ 1 => array('date' => '01:00', 'rx' => 0, 'tx' => 0),
+ 2 => array('date' => '02:00', 'rx' => 0, 'tx' => 0),
+ 3 => array('date' => '03:00', 'rx' => 0, 'tx' => 0),
+ 4 => array('date' => '04:00', 'rx' => 0, 'tx' => 0),
+ 5 => array('date' => '05:00', 'rx' => 0, 'tx' => 0),
+ 6 => array('date' => '06:00', 'rx' => 0, 'tx' => 0),
+ 7 => array('date' => '07:00', 'rx' => 0, 'tx' => 0),
+ 8 => array('date' => '08:00', 'rx' => 0, 'tx' => 0),
+ 9 => array('date' => '09:00', 'rx' => 0, 'tx' => 0),
+ 10 => array('date' => '10:00', 'rx' => 0, 'tx' => 0),
+ 11 => array('date' => '11:00', 'rx' => 0, 'tx' => 0),
+ 12 => array('date' => '12:00', 'rx' => 0, 'tx' => 0),
+ 13 => array('date' => '13:00', 'rx' => 0, 'tx' => 0),
+ 14 => array('date' => '14:00', 'rx' => 0, 'tx' => 0),
+ 15 => array('date' => '15:00', 'rx' => 0, 'tx' => 0),
+ 16 => array('date' => '16:00', 'rx' => 0, 'tx' => 0),
+ 17 => array('date' => '17:00', 'rx' => 0, 'tx' => 0),
+ 18 => array('date' => '18:00', 'rx' => 0, 'tx' => 0),
+ 19 => array('date' => '19:00', 'rx' => 0, 'tx' => 0),
+ 20 => array('date' => '20:00', 'rx' => 0, 'tx' => 0),
+ 21 => array('date' => '21:00', 'rx' => 0, 'tx' => 0),
+ 22 => array('date' => '22:00', 'rx' => 0, 'tx' => 0),
+ 23 => array('date' => '23:00', 'rx' => 0, 'tx' => 0)
+ );
+
+ exec(sprintf('vnstat -i %s --json h', escapeshellarg($interface)), $jsonstdoutvnstat, $exitcodedaily);
+ if ($exitcodedaily !== 0) {
+ exit('vnstat error');
+ }
+
+ $datasizeunits = filter_input(INPUT_GET, 'dsu');
+ $dsu_factor = $datasizeunits == "mb" ? 1024 * 1024 : 1024;
+
+ $jsonobj = json_decode($jsonstdoutvnstat[0], true)['interfaces'][0];
+ $jsonData = $jsonobj['traffic']['hour'];
+ for ($i = count($jsonData) - 1; $i >= 0 && $i >= count($jsonData)-25; --$i) {
+ $data_template[$jsonData[$i]['time']['hour']]['rx'] = round($jsonData[$i]['rx'] / $dsu_factor, 0);
+ $data_template[$jsonData[$i]['time']['hour']]['tx'] = round($jsonData[$i]['tx'] / $dsu_factor, 0);
+ }
+
+ $data = array();
+ $hour = $jsonobj['updated']['time']['hour'];
+ foreach ($data_template as $key => $value) {
+ if ($key > $hour) {
+ array_push($data, $value);
+ }
+ }
+ foreach ($data_template as $key => $value) {
+ if ($key <= $hour) {
+ array_push($data, $value);
+ }
+ }
+ echo json_encode($data);
+ exit(0);
+}
diff --git a/ajax/logging/clearlog.php b/ajax/logging/clearlog.php
new file mode 100644
index 00000000..a5c67385
--- /dev/null
+++ b/ajax/logging/clearlog.php
@@ -0,0 +1,13 @@
+ $path .'/hostapd.conf', "tmp" => "/tmp/hostapddata", "dest" => RASPI_HOSTAPD_CONFIG),
+ array("src" => $path .'/dhcpcd.conf', "tmp" => "/tmp/dhcpddata", "dest" => RASPI_DHCPCD_CONFIG),
+ array("src" => $path .'/090_wlan0.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'wlan0.conf'),
+ array("src" => $path .'/090_raspap.conf', "tmp" => "/tmp/dnsmasqdata", "dest" => RASPI_DNSMASQ_PREFIX.'raspap.conf'),
+ );
+
+ foreach ($configs as $config) {
+ try {
+ $tmp = file_get_contents($config["src"]);
+ file_put_contents($config["tmp"], $tmp);
+ system("sudo cp ".$config["tmp"]. " ".$config["dest"]);
+ } catch (Exception $e) {
+ $return = $e->getCode();
+ }
+ }
+ $jsonData = ['return'=>$return];
+ echo json_encode($jsonData);
+
+} else {
+ handleInvalidCSRFToken();
+}
+
diff --git a/ajax/networking/gen_int_config.php b/ajax/networking/gen_int_config.php
deleted file mode 100644
index c166c529..00000000
--- a/ajax/networking/gen_int_config.php
+++ /dev/null
@@ -1,42 +0,0 @@
- $file) {
- if ($index != "defaults") {
- $cnfFile = parse_ini_file(RASPI_CONFIG_NETWORKING.'/'.$file, false, INI_SCANNER_RAW);
- if ($cnfFile['static'] === 'true') {
- $strConfFile .= "interface ".$cnfFile['interface']."\n";
- $strConfFile .= "static ip_address=".$cnfFile['ip_address']."\n";
- $strConfFile .= "static routers=".$cnfFile['routers']."\n";
- $strConfFile .= "static domain_name_servers=".$cnfFile['domain_name_server']."\n";
- } elseif ($cnfFile['static'] === 'false' && $cnfFile['failover'] === 'true') {
- $strConfFile .= "profile static_".$cnfFile['interface']."\n";
- $strConfFile .= "static ip_address=".$cnfFile['ip_address']."\n";
- $strConfFile .= "static routers=".$cnfFile['routers']."\n";
- $strConfFile .= "static domain_name_servers=".$cnfFile['domain_name_server']."\n\n";
- $strConfFile .= "interface ".$cnfFile['interface']."\n";
- $strConfFile .= "fallback static_".$cnfFile['interface']."\n\n";
- } else {
- $strConfFile .= "#DHCP configured for ".$cnfFile['interface']."\n\n";
- }
- } else {
- $strConfFile .= file_get_contents(RASPI_CONFIG_NETWORKING.'/'.$index)."\n\n";
- }
- }
-
- if (file_put_contents(RASPI_CONFIG_NETWORKING.'/dhcpcd.conf', $strConfFile)) {
- exec('sudo /bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf');
- $output = ['return'=>0,'output'=>'Settings successfully applied'];
- } else {
- $output = ['return'=>2,'output'=>'Unable to write to apply settings'];
- }
- echo json_encode($output);
-}
diff --git a/ajax/networking/get_all_interfaces.php b/ajax/networking/get_all_interfaces.php
index 6924d4d9..b4e18572 100644
--- a/ajax/networking/get_all_interfaces.php
+++ b/ajax/networking/get_all_interfaces.php
@@ -1,6 +1,6 @@
0) {
+ $flags += NL80211_BAND_24GHZ;
+ }
+ if (count(preg_grep('/^5[0-9]{3}/i', $frequencies)) >0) {
+ $flags += NL80211_BAND_5GHZ;
+ }
+
+ switch ($flags) {
+ case NL80211_BAND_24GHZ:
+ $msg = sprintf(_("The selected interface (%s) has support for the 2.4 GHz wireless band only."), $iface);
+ break;
+ case NL80211_BAND_5GHZ:
+ $msg = sprintf(_("The selected interface (%s) has support for the 5 GHz wireless band only."), $iface);
+ break;
+ case NL80211_BAND_24GHZ | NL80211_BAND_5GHZ:
+ $msg = sprintf(_("The selected interface (%s) has support for both the 2.4 and 5 GHz wireless bands."), $iface);
+ break;
+ default:
+ $msg = sprintf(_("The selected interface (%s) does not support wireless mode operation."), $iface);
+ }
+ echo json_encode($msg);
+}
+
diff --git a/ajax/networking/get_int_config.php b/ajax/networking/get_int_config.php
deleted file mode 100644
index ddcef701..00000000
--- a/ajax/networking/get_int_config.php
+++ /dev/null
@@ -1,23 +0,0 @@
-1,'output'=>['intConfig'=>$intConfig]];
- echo json_encode($jsonData);
-
- // Todo - get dhcp lease information from `dhcpcd -U eth0` ? maybe ?
-} else {
- $jsonData = ['return'=>2,'output'=>['Error getting data']];
- echo json_encode($jsonData);
-}
diff --git a/ajax/networking/get_ip_summary.php b/ajax/networking/get_ip_summary.php
index b3b16030..bee214de 100644
--- a/ajax/networking/get_ip_summary.php
+++ b/ajax/networking/get_ip_summary.php
@@ -1,8 +1,8 @@
1) {
+ $dhcpdata['DNS1'] = $arrDns[1];
+ }
+ if (count($arrDns) > 2) {
+ $dhcpdata['DNS2'] = $arrDns[2];
+ }
+ }
+ }
+
+ // fetch dhcpcd.conf settings for interface
+ $conf = file_get_contents(RASPI_DHCPCD_CONFIG);
+ preg_match('/^#\sRaspAP\s'.$interface.'\s.*?(?=\s*+$)/ms', $conf, $matched);
+ preg_match('/metric\s(\d*)/', $matched[0], $metric);
+ preg_match('/static\sip_address=(.*)/', $matched[0], $static_ip);
+ preg_match('/static\srouters=(.*)/', $matched[0], $static_routers);
+ preg_match('/static\sdomain_name_server=(.*)/', $matched[0], $static_dns);
+ preg_match('/fallback\sstatic_'.$interface.'/', $matched[0], $fallback);
+ preg_match('/(?:no)?gateway/', $matched[0], $gateway);
+ $dhcpdata['Metric'] = $metric[1];
+ $dhcpdata['StaticIP'] = strpos($static_ip[1],'/') ? substr($static_ip[1], 0, strpos($static_ip[1],'/')) : $static_ip[1];
+ $dhcpdata['SubnetMask'] = cidr2mask($static_ip[1]);
+ $dhcpdata['StaticRouters'] = $static_routers[1];
+ $dhcpdata['StaticDNS'] = $static_dns[1];
+ $dhcpdata['FallbackEnabled'] = empty($fallback) ? false: true;
+ $dhcpdata['DefaultRoute'] = $gateway[0] == "gateway";
+
+ echo json_encode($dhcpdata);
+}
diff --git a/ajax/networking/get_wgcfg.php b/ajax/networking/get_wgcfg.php
new file mode 100644
index 00000000..6a9d771d
--- /dev/null
+++ b/ajax/networking/get_wgcfg.php
@@ -0,0 +1,9 @@
+ $pubkey_tmp", $return);
+ $wgdata['pubkey'] = str_replace("\n",'',file_get_contents($pubkey_tmp));
+ exec("sudo mv $privkey_tmp $privkey", $return);
+ exec("sudo mv $pubkey_tmp $pubkey", $return);
+
+ echo json_encode($wgdata);
+}
diff --git a/ajax/networking/save_int_config.php b/ajax/networking/save_int_config.php
deleted file mode 100644
index 74716a23..00000000
--- a/ajax/networking/save_int_config.php
+++ /dev/null
@@ -1,34 +0,0 @@
-0,'output'=>['Successfully Updated Network Configuration']];
- } else {
- $jsonData = ['return'=>1,'output'=>['Error saving network configuration to file']];
- }
-} else {
- $jsonData = ['return'=>2,'output'=>'Unable to detect interface'];
-}
-
-echo json_encode($jsonData);
diff --git a/ajax/networking/save_net_dev_config.php b/ajax/networking/save_net_dev_config.php
new file mode 100644
index 00000000..a95baa50
--- /dev/null
+++ b/ajax/networking/save_net_dev_config.php
@@ -0,0 +1,93 @@
+0,'output'=>['Successfully saved mobile data settings']];
+ } else {
+ $jsonData = ['return'=>1,'output'=>['Error saving mobile data settings']];
+ }
+ } else if ( preg_match("/netdevices/",$int)) {
+ if(!isset($_POST['opts']) ) {
+ $jsonData = ['return'=>0,'output'=>['No valid data to add/delete udev rule ']];
+ echo json_encode($jsonData);
+ return;
+ } else {
+ $opts=explode(" ",$_POST['opts'] );
+ $dev=$opts[0];
+ $vid=$_POST["int-vid-".$dev];
+ $pid=$_POST["int-pid-".$dev];
+ $mac=$_POST["int-mac-".$dev];
+ $name=trim($_POST["int-name-".$dev]);
+ // limit device name to letters and numbers. Total length max 20
+ $name=preg_replace("/[^a-z0-9]/", "", strtolower($name));
+ $name=substr($name, 0, min(strlen($name),20));
+ $type=$_POST["int-type-".$dev];
+ $newtype=$_POST["int-new-type-".$dev];
+ $udevfile=$_SESSION["udevrules"]["udev_rules_file"]; // default file /etc/udev/rules.d/80-net-devices.rules";
+
+ // find the rule prototype and prefix
+ $rule = "";
+ foreach($_SESSION["udevrules"]["network_devices"] as $devt) {
+ if($devt["type"]==$newtype) {
+ $rulenew = $devt["udev_rule"];
+ $prefix = $devt["name_prefix"];
+ }
+ }
+
+ // check for an existing rule and delete lines with same MAC or same VID/PID
+ if (!empty($vid) && !empty($pid)) {
+ $rule = '^.*ATTRS{idVendor}==\"' . $vid . '\".*ATTRS{idProduct}==\"' . $pid . '\".*$';
+ exec('sudo sed -i "/'.$rule.'/Id" '.$udevfile); // clear all entries with this VID/PID
+ $rule = '^.*ATTRS{idProduct}==\"' . $pid . '\".*ATTRS{idVendor}==\"' . $vid . '\".*$';
+ exec('sudo sed -i "/'.$rule.'/Id" '.$udevfile); // clear all entries with this VID/PID
+ }
+ if (!empty($mac)) {
+ exec('sudo sed -i "/^.*'.$mac.'.*$/d" '.$udevfile); // clear all entries with same MAC
+ }
+ // create new entry
+ if ( ($type != $newtype) || !empty($name) ) { // new device type or new name
+ if (empty($name)) $name = $prefix."%n";
+ if (!empty($mac)) $rule = preg_replace("/\\\$MAC\\\$/i", $mac, $rulenew);
+ if (!empty($vid)) $rule = preg_replace("/\\\$IDVENDOR\\\$/i", $vid, $rule);
+ if (!empty($pid)) $rule = preg_replace("/\\\$IDPRODUCT\\\$/i", $pid, $rule);
+ if (!empty($name)) $rule = preg_replace("/\\\$DEVNAME\\\$/i",$name,$rule);
+ if (!empty($rule)) exec('echo \''.$rule.'\' | sudo /usr/bin/tee -a '.$udevfile);
+ }
+ $jsonData = ['return'=>0,'output'=>['Settings changed for device '.$dev. '
Changes will only be in effect after reconnecting the device' ] ];
+ }
+ } else {
+ $jsonData = ['return'=>1,'output'=>['Unknown network configuration']];
+ }
+} else {
+ $jsonData = ['return'=>2,'output'=>'Unable to detect interface'];
+}
+
+echo json_encode($jsonData);
diff --git a/ajax/networking/wifi_stations.php b/ajax/networking/wifi_stations.php
index 445e13e0..ecc9c098 100644
--- a/ajax/networking/wifi_stations.php
+++ b/ajax/networking/wifi_stations.php
@@ -1,9 +1,10 @@
$network) $networks[$ssid]["ssidutf8"] = ssid2utf8( $ssid );
-echo renderTemplate('wifi_stations', compact('networks'));
+$connected = array_filter($networks, function($n) { return $n['connected']; } );
+$known = array_filter($networks, function($n) { return !$n['connected'] && $n['configured']; } );
+$nearby = array_filter($networks, function($n) { return !$n['configured']; } );
+
+echo renderTemplate('wifi_stations', compact('networks', 'connected', 'known', 'nearby'), true);
diff --git a/ajax/openvpn/activate_ovpncfg.php b/ajax/openvpn/activate_ovpncfg.php
new file mode 100644
index 00000000..06cff8b9
--- /dev/null
+++ b/ajax/openvpn/activate_ovpncfg.php
@@ -0,0 +1,27 @@
+$return];
+ echo json_encode($jsonData);
+}
+
diff --git a/app/css/all.css b/app/css/all.css
new file mode 100644
index 00000000..12a47159
--- /dev/null
+++ b/app/css/all.css
@@ -0,0 +1,230 @@
+/*
+Name: all.css
+Author: @billz
+Author URI: https://github.com/billz
+Description: Classes shared by all themes
+License: GNU General Public License v3.0
+*/
+
+/* Small devices (portrait phones, up to 576px) */
+@media (max-width: 576px) {
+ .container-fluid, .card-body, .col-md-6 { padding-left: 0.5rem; padding-right: 0.5rem; }
+ .card .card-header { padding: .75rem .5rem; font-size: 1.0rem; }
+ .row { margin-left: 0rem; margin-right: 0rem; }
+ .col-lg-12 { padding-right: 0.25rem; padding-left: 0.25rem; }
+ .form-group.col-md-6 { margin-left: -0.5rem; }
+ h4.mt-3 { margin-left: 0.5rem; }
+}
+
+.sidebar-brand-text {
+ text-transform: none;
+ color: #212529;
+ font-size: 2.0rem;
+ font-weight: 500;
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+.h-underlined {
+ border-bottom: 1px solid #e3e6f0;
+ padding-bottom: 0.3rem;
+}
+
+.navbar-logo {
+ margin-top: 0.5em;
+ margin-left: 0.5em;
+}
+
+.page-header {
+ font-size: 26pt;
+ margin: 20px 0 20px;
+}
+
+.info-item {
+ text-transform: uppercase;
+ font-size: 0.7em;
+ color: #858796;
+}
+
+.info-value {
+ font-size: 0.7rem;
+ margin-left: 0.7rem;
+}
+
+.info-item-xs {
+ font-size: 0.7rem;
+ margin-left: 0.3rem;
+}
+
+.info-item-wifi {
+ width: 6rem;
+ float: left;
+}
+
+.service-status {
+ border-width: 0;
+ align-items: center;
+}
+
+.service-status-up {
+ color: #a1ec38 !important;
+}
+
+.service-status-warn {
+ color: #f6f044 !important;
+}
+
+.service-status-down {
+ color: #f80107 !important;
+ animation: flash 1s linear infinite;
+}
+@keyframes flash {
+ 50% {
+ opacity: 0;
+ }
+}
+
+.logoutput {
+ width:100%;
+ height: 20rem;
+ border: 1px solid #d1d3e2;
+ border-radius: .35rem;
+}
+
+.dhcp-static-leases {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.dhcp-static-lease-row {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+.loading-spinner {
+ background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
+ min-height: 450px;
+ width: 100%;
+}
+
+@media (min-width: 576px) {
+ .card-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 50%;
+ grid-gap: 1rem;
+ }
+}
+
+.toggle-off.btn {
+ padding-left: 1.2rem;
+ font-size: 0.9rem!important;
+}
+
+.toggle-on.btn {
+ font-size: 0.9rem!important;
+}
+
+canvas#divDBChartBandwidthhourly {
+ height: 350px!important;
+}
+
+.chart-container {
+ height: 150px;
+ width: 200px;
+}
+
+.dbChart {
+ display: flex;
+ height: 80%;
+}
+
+.table {
+ margin-bottom: 0rem;
+}
+
+.check-hidden {
+ visibility: hidden;
+}
+
+.check-progress {
+ color: #999;
+}
+
+.fa-check {
+ color: #90ee90;
+}
+
+.fa-times {
+ color: #ff4500;
+}
+
+button.btn.btn-light.js-toggle-password {
+ border: 1px solid lightgrey;
+}
+
+.signal-icon {
+ margin-top: 2px;
+ height: 16px;
+ width: 16px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: baseline;
+}
+.signal-icon .signal-bar {
+ width: 4px;
+ border-radius: 1px;
+ opacity: 30%;
+ background: ;
+}
+
+.signal-icon .signal-bar:nth-child(1) { height: 40%; }
+.signal-icon .signal-bar:nth-child(2) { height: 70%; }
+.signal-icon .signal-bar:nth-child(3) { height: 100%; }
+
+.signal-icon.weak .signal-bar:nth-child(1),
+.signal-icon.medium .signal-bar:nth-child(1),
+.signal-icon.medium .signal-bar:nth-child(2),
+.signal-icon.strong .signal-bar:nth-child(1),
+.signal-icon.strong .signal-bar:nth-child(2),
+.signal-icon.strong .signal-bar:nth-child(3)
+{ opacity: 100%; }.signal-icon {
+ margin-top: 2px;
+ height: 16px;
+ width: 16px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: baseline;
+}
+.signal-icon .signal-bar {
+ width: 4px;
+ border-radius: 1px;
+ opacity: 30%;
+}
+
+.signal-icon .signal-bar:nth-child(1) { height: 40%; }
+.signal-icon .signal-bar:nth-child(2) { height: 70%; }
+.signal-icon .signal-bar:nth-child(3) { height: 100%; }
+
+.signal-icon.weak .signal-bar:nth-child(1),
+.signal-icon.medium .signal-bar:nth-child(1),
+.signal-icon.medium .signal-bar:nth-child(2),
+.signal-icon.strong .signal-bar:nth-child(1),
+.signal-icon.strong .signal-bar:nth-child(2),
+.signal-icon.strong .signal-bar:nth-child(3)
+{ opacity: 100%; }
+
+.gs-edit {
+ border: 1px dashed #ccc;
+ background-color: #f1faee;
+ border-radius: 4px;
+}
+
+figcaption.figure-caption a {
+ color: #858796;
+}
+
+button > i.fas {
+ pointer-events: none;
+}
+
diff --git a/app/css/custom.css b/app/css/custom.css
deleted file mode 100644
index c20ebfa0..00000000
--- a/app/css/custom.css
+++ /dev/null
@@ -1,193 +0,0 @@
-body {
- color: #212529;
-}
-
-.page-header {
- margin: 20px 0 20px;
-}
-
-.page-header .logo {
- margin-right: 5px;
-}
-
-/* Small devices (portrait phones, up to 576px) */
-@media (max-width: 576px) {
- .container-fluid, .card-body, .col-md-6 { padding-left: 0.5rem; padding-right: 0.5rem; }
- .card .card-header { padding: .75rem .5rem; font-size: 1.0rem; }
- .row { margin-left: 0rem; margin-right: 0rem; }
- .col-lg-12 { padding-right: 0.25rem; padding-left: 0.25rem; }
- .form-group.col-md-6 { margin-left: -0.5rem; }
- .js-wifi-stations { margin-left: -0.5rem; margin-right: -0.5rem; }
- h4.mt-3 { margin-left: 0.5rem; }
-}
-
-.sidebar {
- background-color: #f8f9fc;
-}
-
-.sidebar-brand-text {
- text-transform: none;
- color: #212529;
- font-size: 2.0rem;
- font-weight: 500;
- font-family: Helvetica, Arial, sans-serif;
-}
-
-.sidebar .nav-item.active .nav-link {
- font-weight: 500;
-}
-
-.card .card-header {
- border-color: #d8224c;
- background-color: #d8224c;
- color: #fff;
-}
-
-.card-footer {
- background-color: #f2f1f0;
-}
-
-.nav-item {
- font-size: 0.85rem;
-}
-
-.nav-tabs .nav-link.active,
-.nav-tabs .nav-link {
- font-size: 1.0rem;
-}
-
-.nav-tabs a.nav-link {
- color: #6e707e;
-}
-
-a.nav-link.active {
- font-weight: bolder;
-}
-
-.sidebar .nav-item .nav-link {
- padding: 0.6rem;
-}
-
-.alert-success {
- background-color: #d4edda;
-}
-
-.btn-primary {
- color: #d8224c;
- background-color: #fff;
- border-color: #d8224c;
-}
-
-.btn-warning {
- color: #333;
-}
-
-.btn-primary:hover {
- background-color: #c61931;
- border-color: #c61931;
-}
-
-i.fa.fa-bars {
- color: #d1d3e2;
-}
-
-i.fa.fa-bars:hover{
- color: #6e707e;
-}
-
-.info-item {
- width: 10rem;
- float: left;
-}
-
-.info-item-wifi {
- width: 6rem;
- float: left;
-}
-
-.webconsole {
- width:100%;
- height:100%;
- border:1px solid;
-}
-
-#console {
- height:500px;
-}
-
-.systemtabcontent {
- height:100%;
- min-height:500px;
-}
-
-.service-status {
- border-width: 0;
-}
-
-.service-status-up {
- color: green;
-}
-
-.service-status-down {
- color: red;
- animation: flash 1s linear infinite;
-}
-@keyframes flash {
- 50% {
- opacity: 0;
- }
-}
-
-.logoutput {
- width:100%;
- height: 20rem;
- border: 1px solid #d1d3e2;
- border-radius: .35rem;
-}
-
-pre.unstyled {
- border-width: 0;
- background-color: transparent;
- padding: 0;
-}
-
-.dhcp-static-leases {
- margin-top: 1em;
- margin-bottom: 1em;
-}
-
-.dhcp-static-lease-row {
- margin-top: 0.5em;
- margin-bottom: 0.5em;
-}
-
-.loading-spinner {
- background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
- min-height: 150px;
- width: 100%;
-}
-
-.js-reload-wifi-stations {
- min-width: 10rem;
-}
-
-.sidebar.toggled .nav-item .nav-link span {
- display: none;
-} .sidebar .nav-item .nav-link i,
-.sidebar .nav-item .nav-link span {
- font-size: 1.0rem;
-}
-
-.btn-warning:hover {
- color: #000;
-}
-
-.toggle-off.btn {
- padding-left: 1.2rem;
- font-size: 0.9rem!important;
-}
-
-.toggle-on.btn {
- font-size: 0.9rem!important;
-}
-
diff --git a/app/css/custom.php b/app/css/custom.php
new file mode 100644
index 00000000..ff87b3b2
--- /dev/null
+++ b/app/css/custom.php
@@ -0,0 +1,115 @@
+
+
+
+/*
+Theme Name: RaspAP default
+Author: @billz
+Author URI: https://github.com/billz
+Description: Default theme for RaspAP
+License: GNU General Public License v3.0
+*/
+
+@import url('all.css');
+
+body {
+ color: #212529;
+}
+
+.sidebar {
+ background-color: #f8f9fc;
+}
+
+.sidebar .nav-item.active .nav-link {
+ font-weight: 500;
+}
+
+.card .card-header, .modal-header {
+ border-color: ;
+ color: #fff;
+ background-color: ;
+}
+
+.modal-header {
+ border-radius: 0px;
+}
+
+.btn-primary {
+ color: ;
+ border-color: ;
+ background-color: #fff;
+}
+
+.card-footer, .modal-footer {
+ background-color: #f2f1f0;
+}
+
+.nav-item {
+ font-size: 0.85rem;
+}
+
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-link {
+ font-size: 1.0rem;
+}
+
+.nav-tabs a.nav-link {
+ color: #6e707e;
+}
+
+a.nav-link.active {
+ font-weight: bolder;
+}
+
+.sidebar .nav-item .nav-link {
+ padding: 0.6rem 0.6rem 0.6rem 1.0rem;
+}
+
+.alert-success {
+ background-color: #d4edda;
+}
+
+.btn-primary {
+ background-color: #fff;
+}
+
+.btn-warning {
+ color: #333;
+}
+
+.btn-primary:hover {
+ background-color: ;
+ border-color: ;
+}
+
+i.fa.fa-bars {
+ color: #d1d3e2;
+}
+
+i.fa.fa-bars:hover{
+ color: #6e707e;
+}
+
+pre.unstyled {
+ border-width: 0;
+ background-color: transparent;
+ padding: 0;
+}
+
+.sidebar.toggled .nav-item .nav-link span {
+ display: none;
+} .sidebar .nav-item .nav-link i,
+.sidebar .nav-item .nav-link span {
+ font-size: 1.0rem;
+}
+
+.btn-warning:hover {
+ color: #000;
+}
+
+.signal-icon .signal-bar {
+ background: ;
+}
+
diff --git a/app/css/hackernews.css b/app/css/hackernews.css
index d5f7ae63..76a55370 100644
--- a/app/css/hackernews.css
+++ b/app/css/hackernews.css
@@ -1,6 +1,15 @@
+/*
+Theme Name: HackerNews
+Author: @billz
+Author URI: https://github.com/billz
+Description: A theme inspired by HackerNews for RaspAP
+License: GNU General Public License v3.0
+*/
+
+@import url('all.css');
+
html * {
font-family: Verdana, Geneva, sans-serif;
- font-size: 0.9rem;
color: #828282;
}
@@ -12,6 +21,10 @@ a:focus, a:hover {
color: #666;
}
+h2 {
+ font-size: 2rem !important;
+}
+
h4 {
font-size: 1.3rem;
color: #212529;
@@ -22,12 +35,12 @@ h5.card-title {
color: #212529;
}
-.card {
- border-radius: 1px;
+.card, .modal-dialog {
+ border-radius: 3px;
border-color: #ff6600;
}
-.card>.card-header {
+.card>.card-header, .modal-header {
border-color: #ff6600;
background-color: #ff6600;
color: #000;
@@ -41,19 +54,23 @@ h5.card-title {
font-size: 1.0rem;
}
-.card-header [class^="fa"] {
+.card-header [class^="fa"], .modal-header [class^="fa"] {
color: #fff;
font-size: 1.0rem;
}
-.sidebar-brand-text {
- text-transform: none;
- color: #212529;
- font-size: 2.0rem;
- font-weight: 500;
- font-family: Verdana, Geneva, sans-serif;
+.modal-title {
+ color: #000;
+ font-size: 1.0rem;
}
+.modal-content {
+ border-radius: 0px;
+}
+
+.sidebar-light hr.sidebar-divider {
+ padding-top: 0.5rem;
+}
ul.nav-tabs, .nav-tabs .nav-link {
background-color: #f6f6ef;
@@ -62,34 +79,19 @@ ul.nav-tabs, .nav-tabs .nav-link {
.sidebar .nav-item .nav-link {
padding: 0.6rem;
+ margin-left: 0.6rem;
}
.sidebar-light .nav-item.active .nav-link {
font-weight: 300;
}
-.page-header {
- font-size: 26pt;
- margin: 10px 0 20px;
-}
-
#wrapper,#page-wrapper,
#wrapper #content-wrapper,
.nav>li>a,.nav {
background-color: #fff;
}
-/* Small devices (portrait phones, up to 576px) */
-@media (max-width: 576px) {
- .container-fluid, .card-body, .col-md-6 { padding-left: 0.5rem; padding-right: 0.5rem; }
- .card .card-header { padding: .75rem .5rem; font-size: 1.0rem; }
- .row { margin-left: 0rem; margin-right: 0rem; }
- .col-lg-12 { padding-right: 0.25rem; padding-left: 0.25rem; }
- .form-group.col-md-6 { margin-left: -0.5rem; }
- .js-wifi-stations { margin-left: -0.5rem; margin-right: -0.5rem; }
- h4.mt-3 { margin-left: 0.5rem; }
-}
-
.card-body {
background-color: #f6f6ef;
}
@@ -120,44 +122,10 @@ ul.nav-tabs, .nav-tabs .nav-link {
color: #eee;
}
-.info-item {
- width: 10rem;
- float: left;
+.fas.fa-circle {
+ font-size: 0.7rem;
}
-.info-item-wifi {
- width: 6rem;
- float: left;
-}
-
-.logoutput {
- width: 100%;
- height: 20rem;
- border: 1px solid #d1d3e2;
- border-radius: .35rem;
-}
-
-.service-status {
- border-width: 0;
-}
-
-i.fas.fa-circle.service-status-up {
- color: green;
-}
-
-i.fas.fa-circle.service-status-down {
- color: red;
-}
-
-.service-status-down {
- animation: flash 1s linear infinite;
-}
-@keyframes flash {
- 50% {
- opacity: 0;
- }
-}
-
.logoutput {
width:100%;
height:300px;
@@ -169,26 +137,6 @@ pre.unstyled {
padding: 0;
}
-.dhcp-static-leases {
- margin-top: 1em;
- margin-bottom: 1em;
-}
-
-.dhcp-static-lease-row {
- margin-top: 0.5em;
- margin-bottom: 0.5em;
-}
-
-.loading-spinner {
- background: url("../../app/img/loading-spinner.gif") no-repeat scroll center center transparent;
- min-height: 150px;
- width: 100%;
-}
-
-.js-reload-wifi-stations {
- min-width: 10rem;
-}
-
.sidebar.toggled .nav-item .nav-link {
text-align: center;
padding: .6rem 1rem;
@@ -206,12 +154,7 @@ pre.unstyled {
color: #000;
}
-.toggle-off.btn {
- padding-left: 0.9rem;
- font-size: 0.9rem!important;
-}
-
-.toggle-on.btn {
- font-size: 0.9rem!important;
+.signal-icon .signal-bar {
+ background: #ff6600;
}
diff --git a/app/css/lightsout.css b/app/css/lightsout.css
new file mode 100644
index 00000000..aea5c83d
--- /dev/null
+++ b/app/css/lightsout.css
@@ -0,0 +1,360 @@
+/*
+Theme Name: Lights Out
+Author: @billz
+Author URI: https://github.com/billz
+Description: A dark mode theme for RaspAP
+License: GNU General Public License v3.0
+*/
+
+@import url('all.css');
+
+html * {
+ font-family: Helvetica,Arial,sans-serif;
+ color: #afafaf;
+}
+
+h2 {
+ font-size: 2rem !important;
+}
+
+h4 {
+ font-size: 1.3rem;
+}
+
+h5.card-title {
+ font-size: 1.2rem;
+}
+
+.page-header {
+ border-left: .01rem solid #d2d2d2;
+ border-bottom: .01rem solid #d2d2d2;
+}
+
+.sidebar-light .nav-item.active .nav-link i {
+ color: #d2d2d2;
+}
+
+.sidebar .nav-item.active .nav-link {
+ font-weight: 400;
+}
+
+#wrapper #content-wrapper #content {
+ background-color: #202020;
+}
+
+.topbar {
+ background-color: #202020;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #404040;
+}
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-link {
+ font-size: 1.0rem;
+}
+
+.nav-tabs .nav-link:hover {
+ border-color: transparent;
+}
+
+.navbar-default .navbar-brand:hover {
+ color: #d2d2d2;
+}
+
+.navbar-default .navbar-toggle {
+ border-color: #d2d2d2;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #d2d2d2;
+}
+
+.navbar-default .navbar-toggle:focus,
+.navbar-default .navbar-toggle:hover {
+ background-color: #202020;
+}
+
+#content, .navbar, .sidebar, .footer, .sticky-footer {
+ background-attachment: scroll;
+ background-repeat: repeat;
+ background-size: auto;
+ background-position: 0 0;
+ background-origin: padding-box;
+ background-clip: border-box;
+}
+
+.sticky-footer {
+ background-position: 30px 0;
+}
+
+.sidebar {
+ background-position: 0 20px;
+}
+
+.nav-tabs .nav-link.active {
+ color: #d2d2d2;
+ background-color: #141414;
+ border-color: #404040 #404040 #141414;
+}
+
+a:focus, a:hover {
+ color: #d2d2d2;
+}
+
+.card>.card-header, .modal-content, .modal-header {
+ border-color: #404040;
+ background-color: #202020;
+ color: #afafaf;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+ font-size: 1.0rem;
+ font-weight: 400;
+}
+
+.modal-body {
+ background-color: #141414;
+}
+
+.card>.card-header .fa {
+ color: #202020;
+}
+
+.card-header [class^="fa"] {
+ color: #afafaf;
+ font-size: 1.0rem;
+}
+
+.col {
+ color: #afafaf;
+}
+
+.card, .card-body {
+ border-color: #343434;
+ border-radius: 3px;
+ background-color: #141414;
+}
+
+hr {
+ border-top: .01rem solid #d2d2d2;
+}
+
+.sidebar-brand-text {
+ color: #2b8080 !important;
+}
+
+.ra-raspap:before {
+ color: #ac1b3d !important;
+}
+
+.sidebar-light #sidebarToggle {
+ background-color: #202020;
+ border: 1px solid #afafaf !important
+}
+
+.sidebar-light #sidebarToggle::after {
+ color: #afafaf;
+}
+
+.sidebar-light .nav-item .nav-link:hover i {
+ color: #d2d2d2;
+}
+
+.sidebar-light #sidebarToggle:hover {
+ background-color: #202020;
+}
+
+.sidebar.toggled .nav-item .nav-link span {
+ display: none;
+}
+
+.sidebar.toggled .nav-item .nav-link {
+ text-align: center;
+ padding: .6rem 1rem;
+ width: 6.5rem;
+}
+
+.card-footer, .modal-footer {
+ background-color: #202020;
+ border-top: 0px;
+}
+
+.modal-footer {
+ border-radius: 0.3rem;
+}
+
+.card>.card-header::before, .navbar-default::before {
+ content: " ";
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 2;
+ background-size: 100% 2px, 3px 100%;
+ pointer-events: none;
+}
+
+.sidebar-light, .sticky-footer {
+ background-color: #202020;
+}
+
+.sidebar-light .nav-item .nav-link i {
+ color: rgba(230, 230, 230, .3);
+}
+
+.sidebar .nav-item .nav-link {
+ padding: 0.6rem;
+ padding-left: 1.2rem;
+}
+
+.sidebar-light hr.sidebar-divider {
+ border-top: 1px solid #404040;
+ padding-top: 0.5rem;
+}
+
+.sidebar .nav-item .nav-link span {
+ font-size: 1.0rem;
+}
+
+.topbar .topbar-divider {
+ border-right: 1px solid #404040;
+}
+
+.label-warning {
+ background-color: #d2d2d2;
+}
+
+span.label.label-warning {
+ color: #202020;
+}
+
+.table>tbody>tr>td,
+.table>tbody>tr>th,
+.table>tfoot>tr>td,
+.table>tfoot>tr>th,
+.table>thead>tr>td,
+.table>thead>tr>th {
+ background-color: #202020;
+ border-top: .01rem solid #202020;
+}
+
+.table>thead>tr>th {
+ vertical-align: bottom;
+ border-bottom: .01rem solid #d2d2d2;
+}
+
+[class*="btn"], [class*="btn"]:focus, [class*="btn"]:disabled {
+ background-color: #202020;
+ border-color: #404040;
+ border-radius: 3px;
+ color: #d2d2d2;
+}
+
+[class*="btn"]:hover {
+ border-radius: 3px;
+ color: #d2d2d2;
+ background-color: #202020;
+ border-color: #afafaf;
+}
+
+[class*="btn"]:hover .disabled {
+ background-color:red;
+}
+
+[class*="alert"] {
+ border-radius: .35rem;
+ color: #d2d2d2;
+ background-color: #202020;
+ border: 1px solid #404040;
+}
+
+.close {
+ font-size: 1.2em;
+ font-weight: 400;
+ text-shadow: none;
+ color: #d2d2d2;
+}
+
+.form-control,
+.form-control:focus,
+.custom-select {
+ color: #d2d2d2;
+ background-color: #202020;
+ border: 1px solid #404040;
+ border-radius: 3px;
+}
+
+.form-control:disabled,
+.form-control[readonly] {
+ background-color: #202020;
+ opacity: 0.5;
+}
+
+.form-control::-webkit-input-placeholder { color: #d2d2d2; }
+.form-control:-moz-placeholder { color: #d2d2d2; }
+.form-control::-moz-placeholder { color: #d2d2d2; }
+.form-control:-ms-input-placeholder { color: #d2d2d2; }
+.form-control::-ms-input-placeholder { color: #d2d2d2; }
+
+input[type="text"]{
+color: #d2d2d2 !important
+}
+
+.progress {
+ background-color: #202020;
+ border-radius: 0px;
+}
+
+.progress-bar {
+ color: #202020;
+}
+
+.progress-bar.progress-bar-info.progress-bar-striped.active {
+ background-color: #d2d2d2;
+}
+
+.figure-img {
+ filter: opacity(0.7);
+}
+
+.ra-wireguard:before {
+ color: #404040 !important;
+}
+
+.ra-wireguard:hover:before {
+ color: #d1d3e2 !important;
+}
+
+.sidebar .nav-item.active .nav-link span.ra-wireguard:before {
+ color: #d2d2d2 !important;
+}
+
+.logoutput {
+ background-color: #202020;
+ border-color: #404040;
+}
+
+.text-muted {
+ font-size: 0.8rem;
+}
+
+.fas.fa-circle {
+ font-size: 0.7rem;
+}
+
+pre {
+ background-color: #202020;
+ border: #202020;
+}
+
+button.btn.btn-light.js-toggle-password {
+ border: 1px solid #343434;
+}
+
+
+.signal-icon .signal-bar {
+ background: #2b8080;
+}
+
diff --git a/app/css/terminal.css b/app/css/terminal.css
deleted file mode 100644
index 3ac3a1fb..00000000
--- a/app/css/terminal.css
+++ /dev/null
@@ -1,396 +0,0 @@
-html * {
- font-family: Courier New, Andale Mono, monospace;
- font-size: 1.0rem;
- color: #2ee600;
-}
-
-h4 {
- font-size: 1.3rem;
-}
-
-h5.card-title {
- font-size: 1.2rem;
-}
-
-.page-header {
- padding: 0 20px;
- border-left: 1px solid #2ee600;
-}
-
-.sidebar-light .nav-item.active .nav-link i {
- color: #2ee600;
-}
-
-.sidebar .nav-item.active .nav-link {
- font-weight: 700;
-}
-
-#wrapper #content-wrapper #content {
- background-color: #000;
-}
-
-/* Small devices (portait phones, up to 576px) */
-@media (max-width: 576px) {
- .container-fluid, .card-body, .col-md-6 { padding-left: 0.5rem; padding-right: 0.5rem; }
- .card .card-header { padding: .75rem .5rem; font-size: 1.0rem; }
- .row { margin-left: 0rem; margin-right: 0rem; }
- .col-lg-12 { padding-right: 0.25rem; padding-left: 0.25rem; }
- .form-group.col-md-6 { margin-left: -0.5rem; }
- .js-wifi-stations { margin-left: -0.5rem; margin-right: -0.5rem; }
- h4.mt-3 { margin-left: 0.5rem; }
-}
-
-.topbar {
- background-color: #000;
-}
-
-.nav-tabs .nav-link.active,
-.nav-tabs .nav-link {
- font-size: 1.0rem;
-}
-
-.nav-tabs>li.active>a,
-.nav-tabs>li.active>a:focus,
-.nav-tabs>li.active>a:hover,
-.nav-tabs .nav-link:hover,
-.input-group-addon {
- color: #2ee600;
- cursor: default;
- background-color: #000;
- border: 1px solid #2ee600;
- border-bottom-color: #2ee600;
- border-radius: inherit;
-}
-
-.nav-tabs>li>a,.nav-tabs>li>a:hover {
- border: 1px solid #2ee600;
-}
-
-.nav-tabs {
- border-bottom: 1px solid #2ee600;
-}
-
-.navbar-default .navbar-brand,
-.navbar-default .navbar-brand:hover {
- color: #2ee600;
-}
-
-.navbar-default .navbar-toggle {
- border-color: #2ee600;
-}
-
-.navbar-default .navbar-toggle .icon-bar {
- background-color: #2ee600;
-}
-
-.navbar-default .navbar-toggle:focus,
-.navbar-default .navbar-toggle:hover {
- background-color: #000;
-}
-
-.nav-tabs .nav-link.active {
- color: #000;
- background-color: #2ee600;
- border-color: #2ee600;
- border-radius: inherit;
-}
-
-.sidebar-brand-icon {
- filter: invert(65%) sepia(900%) saturate(536%) hue-rotate(68deg) brightness(100%) contrast(120%);
- width: 30px;
-}
-
-a:focus, a:hover {
- color: #2ee600;
-}
-
-.card>.card-header {
- border-color: #2ee600;
- background-color: #2ee600;
- color: #000;
- border-radius: unset;
- font-size: 1.1rem;
- font-weight: bold;
-}
-
-.card>.card-header .fa {
- color: #000;
-}
-
-.card-header [class^="fa"] {
- color: #000;
- font-size: 1.0rem;
-}
-
-.col {
- color: #000;
-}
-
-.card, .card-body {
- margin-bottom: 20px;
- border: 1px solid #2ee600;
- border-radius: 0px;
- background-color: #000;
-}
-
-hr {
- border-top: 1px solid #2ee600;
-}
-
-.page-header {
- font-size: 24pt;
- margin: 10px 0 20px;
- border-bottom: 1px solid #2ee600;
-}
-
-.sidebar-brand-text {
- text-transform: none;
- color: #2ee600;
- font-size: 2.0rem;
- font-weight: 500;
- font-family: inherit;
-}
-
-.sidebar-light #sidebarToggle {
- background-color: #2ee600;
-}
-
-.sidebar-light #sidebarToggle::after {
- color: #000;
-}
-
-.sidebar-light .nav-item .nav-link:hover i {
- color: #81ff61;
-}
-
-.sidebar-light #sidebarToggle:hover {
- background-color: #81ff61;
-}
-
-.sidebar.toggled .nav-item .nav-link span {
- display: none;
-}
-
-.sidebar.toggled .nav-item .nav-link {
- text-align: center;
- padding: .6rem 1rem;
- width: 6.5rem;
-}
-
-.card-footer {
- background-color: #000;
- border-top: 1px solid #2ee600;
-}
-
-.card>.card-header::before, .navbar-default::before {
- content: " ";
- display: block;
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
- z-index: 2;
- background-size: 100% 2px, 3px 100%;
- pointer-events: none;
-}
-
-.sidebar-light, .sticky-footer {
- background-color: #000;
-}
-
-.sidebar-light .nav-item .nav-link i {
- color: #2ee600;
-}
-
-.sidebar .nav-item .nav-link {
- padding: 0.6rem;
-}
-
-.sidebar-light hr.sidebar-divider {
- border-top: 1px solid #2ee600;
-}
-
-.topbar .topbar-divider {
- border-right: 1px solid #2ee600;
-}
-
-.info-item {
- width: 12rem;
- float: left;
-}
-
-.info-item-wifi {
- width: 6rem;
- float: left;
-}
-
-.label-warning {
- background-color: #2ee600;
-}
-
-span.label.label-warning {
- color: #000;
-}
-
-.table>tbody>tr>td,
-.table>tbody>tr>th,
-.table>tfoot>tr>td,
-.table>tfoot>tr>th,
-.table>thead>tr>td,
-.table>thead>tr>th {
- background-color: #000;
- border-top: 1px solid #000;
-}
-
-.table>thead>tr>th {
- vertical-align: bottom;
- border-bottom: 1px solid #2ee600;
-}
-
-[class*="btn"], [class*="btn"]:focus, [class*="btn"]:disabled {
- background-color: #000;
- border-color: #2ee600;
- border-color: #2ee600;
- border-radius: 0px;
- color: #2ee600;
-}
-
-[class*="btn"]:hover {
- background-color: #81ff61;
- border-color: #81ff61;
- border-color: #81ff61;
- border-radius: 0px;
- color: #000;
-}
-
-[class*="btn"]:hover .disabled {
- background-color:red;
-}
-
-[class*="alert"] {
- border-radius: 0px;
- color: #2ee600;
- background-color: #000;
- border-color: #2ee600;
- border: 1px dashed;
-}
-
-.close {
- font-size: 18px;
- font-weight: normal;
- text-shadow: 0 0px 0 #000;
- opacity: 1;
-}
-
-.form-control,
-.form-control:focus {
- color: #2ee600;
- background-color: #000;
- border: 1px solid #2ee600;
- border-radius: 0px;
- transition: unset;
-}
-
-.form-control:disabled,
-.form-control[readonly] {
- background-color: #000;
- opacity: 0.5;
-}
-
-.form-control::-webkit-input-placeholder { color: #2ee600; }
-.form-control:-moz-placeholder { color: #2ee600; }
-.form-control::-moz-placeholder { color: #2ee600; }
-.form-control:-ms-input-placeholder { color: #2ee600; }
-.form-control::-ms-input-placeholder { color: #2ee600; }
-
-input[type="text"]{
-color: #2ee600 !important
-}
-
-.progress {
- background-color: #000;
- border-radius: 0px;
-}
-
-.progress-bar {
- color: #000;
-}
-
-.progress-bar.progress-bar-info.progress-bar-striped.active {
- background-color: #2ee600;
-}
-
-.logoutput {
- width: 100%;
- height: 300px;
- background-color: #000;
- border-color: #2ee600;
-}
-
-.webconsole {
- width: 100%;
- height: 100%;
- border-color: #2ee600;
- border-bottom: 1px solid;
- border-left: 1px solid;
- border-top: 0px;
- border-right: 1px solid;
-}
-
-#console {
- height: 500px;
-}
-
-tspan, rect {
- fill: #2ee600;
-}
-
-.service-status {
- border-width: 0;
-}
-
-span.text.service-status {
- font-size: 0.75rem;
- margin-top: 0.2rem;
-}
-
-i.fas.fa-circle.service-status-up {
- color: #2ee600;
-}
-
-i.fas.fa-circle.service-status-down {
- color: #2ee600;
- animation: flash 1s linear infinite;
-}
-@keyframes flash {
- 50% {
- opacity: 0;
- }
-}
-
-pre {
- background-color: #000;
- border: #000;
-}
-
-.dhcp-static-leases {
- margin-top: 1em;
- margin-bottom: 1em;
-}
-
-.dhcp-static-lease-row {
- margin-top: 0.5em;
- margin-bottom: 0.5em;
-}
-
-.toggle-off.btn {
- padding-left: 1.2rem;
- font-size: 0.9rem!important;
-}
-
-.toggle-on.btn {
- font-size: 0.9rem!important;
-}
-
diff --git a/app/icons/site.webmanifest b/app/icons/site.webmanifest
index 321ab7db..5d0d01c4 100644
--- a/app/icons/site.webmanifest
+++ b/app/icons/site.webmanifest
@@ -3,7 +3,7 @@
"short_name": "RaspAP",
"icons": [
{
- "src": "/dist/icons/android-chrome-192x192.png",
+ "src": "/app/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
}
diff --git a/app/img/authors-8bit-200px.png b/app/img/authors-8bit-200px.png
deleted file mode 100644
index 72c6f1b3..00000000
Binary files a/app/img/authors-8bit-200px.png and /dev/null differ
diff --git a/app/img/insiders.png b/app/img/insiders.png
new file mode 100644
index 00000000..6ad27f35
Binary files /dev/null and b/app/img/insiders.png differ
diff --git a/app/img/no-trace-200x200.png b/app/img/no-trace-200x200.png
new file mode 100644
index 00000000..ab1b40e4
Binary files /dev/null and b/app/img/no-trace-200x200.png differ
diff --git a/app/img/raspAP-logo.php b/app/img/raspAP-logo.php
new file mode 100644
index 00000000..c23399ab
--- /dev/null
+++ b/app/img/raspAP-logo.php
@@ -0,0 +1,51 @@
+
+
+
+
diff --git a/app/img/raspAP-logo.png b/app/img/raspAP-logo.png
index 3943725f..d1da5e19 100644
Binary files a/app/img/raspAP-logo.png and b/app/img/raspAP-logo.png differ
diff --git a/app/img/raspAP-logo64px.png b/app/img/raspAP-logo64px.png
deleted file mode 100644
index 3c424683..00000000
Binary files a/app/img/raspAP-logo64px.png and /dev/null differ
diff --git a/app/img/wg-qr-code.php b/app/img/wg-qr-code.php
new file mode 100644
index 00000000..7a66e6fc
--- /dev/null
+++ b/app/img/wg-qr-code.php
@@ -0,0 +1,28 @@
+'));
} else if(jsonData['return'] == 2) {
@@ -49,91 +47,24 @@ function setupTabs() {
});
}
-function loadCurrentSettings(strInterface) {
- $.post('ajax/networking/get_int_config.php',{interface:strInterface},function(data){
- jsonData = JSON.parse(data);
- $.each(jsonData['output'],function(i,v) {
- var int = v['interface'];
- $.each(v,function(i2,v2) {
- switch(i2) {
- case "static":
- if(v2 == 'true') {
- $('#'+int+'-static').click();
- $('#'+int+'-nofailover').click();
- } else {
- $('#'+int+'-dhcp').click();
- }
- break;
- case "failover":
- if(v2 === 'true') {
- $('#'+int+'-failover').click();
- } else {
- $('#'+int+'-nofailover').click();
- }
- break;
- case "ip_address":
- var arrIPNetmask = v2.split('/');
- $('#'+int+'-ipaddress').val(arrIPNetmask[0]);
- $('#'+int+'-netmask').val(createNetmaskAddr(arrIPNetmask[1]));
- break;
- case "routers":
- $('#'+int+'-gateway').val(v2);
- break;
- case "domain_name_server":
- svrsDNS = v2.split(" ");
- $('#'+int+'-dnssvr').val(svrsDNS[0]);
- $('#'+int+'-dnssvralt').val(svrsDNS[1]);
- break;
- }
- });
- });
- });
-}
-
-function saveNetworkSettings(int) {
- var frmInt = $('#frm-'+int).find(':input');
- var arrFormData = {};
- $.each(frmInt,function(i3,v3){
- if($(v3).attr('type') == 'radio') {
- arrFormData[$(v3).attr('id')] = $(v3).prop('checked');
- } else {
- arrFormData[$(v3).attr('id')] = $(v3).val();
- }
- });
- arrFormData['interface'] = int;
- $.post('ajax/networking/save_int_config.php',arrFormData,function(data){
- var jsonData = JSON.parse(data);
- $('#msgNetworking').html(msgShow(jsonData['return'],jsonData['output']));
- });
-}
-
-function applyNetworkSettings() {
- var int = $(this).data('int');
- arrFormData = {};
- arrFormData['generate'] = '';
- $.post('ajax/networking/gen_int_config.php',arrFormData,function(data){
- console.log(data);
- var jsonData = JSON.parse(data);
- $('#msgNetworking').html(msgShow(jsonData['return'],jsonData['output']));
- });
-}
-
$(document).on("click", ".js-add-dhcp-static-lease", function(e) {
e.preventDefault();
var container = $(".js-new-dhcp-static-lease");
var mac = $("input[name=mac]", container).val().trim();
var ip = $("input[name=ip]", container).val().trim();
+ var comment = $("input[name=comment]", container).val().trim();
if (mac == "" || ip == "") {
return;
}
-
var row = $("#js-dhcp-static-lease-row").html()
.replace("{{ mac }}", mac)
- .replace("{{ ip }}", ip);
+ .replace("{{ ip }}", ip)
+ .replace("{{ comment }}", comment);
$(".js-dhcp-static-lease-container").append(row);
$("input[name=mac]", container).val("");
$("input[name=ip]", container).val("");
+ $("input[name=comment]", container).val("");
});
$(document).on("click", ".js-remove-dhcp-static-lease", function(e) {
@@ -145,14 +76,90 @@ $(document).on("submit", ".js-dhcp-settings-form", function(e) {
$(".js-add-dhcp-static-lease").trigger("click");
});
+$(document).on("click", ".js-add-dhcp-upstream-server", function(e) {
+ e.preventDefault();
+
+ var field = $("#add-dhcp-upstream-server-field")
+ var row = $("#dhcp-upstream-server").html().replace("{{ server }}", field.val())
+
+ if (field.val().trim() == "") { return }
+
+ $(".js-dhcp-upstream-servers").append(row)
+
+ field.val("")
+});
+
+$(document).on("click", ".js-remove-dhcp-upstream-server", function(e) {
+ e.preventDefault();
+ $(this).parents(".js-dhcp-upstream-server").remove();
+});
+
+$(document).on("submit", ".js-dhcp-settings-form", function(e) {
+ $(".js-add-dhcp-upstream-server").trigger("click");
+});
+
+/**
+ * mark a form field, e.g. a select box, with the class `.js-field-preset`
+ * and give it an attribute `data-field-preset-target` with a text field's
+ * css selector.
+ *
+ * now, if the element marked `.js-field-preset` receives a `change` event,
+ * its value will be copied to all elements matching the selector in
+ * data-field-preset-target.
+ */
+$(document).on("change", ".js-field-preset", function(e) {
+ var selector = this.getAttribute("data-field-preset-target")
+ var value = "" + this.value
+ var syncValue = function(el) { el.value = value }
+
+ if (value.trim() === "") { return }
+
+ document.querySelectorAll(selector).forEach(syncValue)
+});
+
+$(document).on("click", "#gen_wpa_passphrase", function(e) {
+ $('#txtwpapassphrase').val(genPassword(63));
+});
+
+$(document).on("click", "#js-clearhostapd-log", function(e) {
+ $.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log'},function(data){
+ jsonData = JSON.parse(data);
+ $("#hostapd-log").val("");
+ });
+});
+
+$(document).on("click", "#js-cleardnsmasq-log", function(e) {
+ $.post('ajax/logging/clearlog.php?',{'logfile':'/var/log/dnsmasq.log'},function(data){
+ jsonData = JSON.parse(data);
+ $("#dnsmasq-log").val("");
+ });
+});
+
+$(document).on("click", "#js-clearopenvpn-log", function(e) {
+ $.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/openvpn.log'},function(data){
+ jsonData = JSON.parse(data);
+ $("#openvpn-log").val("");
+ });
+});
+
+
+// Enable Bootstrap tooltips
+$(function () {
+ $('[data-toggle="tooltip"]').tooltip()
+})
+
+function genPassword(pwdLen) {
+ var pwdChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ var rndPass = Array(pwdLen).fill(pwdChars).map(function(x) { return x[Math.floor(Math.random() * x.length)] }).join('');
+ return rndPass;
+}
+
function setupBtns() {
$('#btnSummaryRefresh').click(function(){getAllInterfaces();});
-
$('.intsave').click(function(){
var int = $(this).data('int');
saveNetworkSettings(int);
});
-
$('.intapply').click(function(){
applyNetworkSettings();
});
@@ -166,13 +173,19 @@ function setCSRFTokenHeader(event, xhr, settings) {
}
function contentLoaded() {
- pageCurrent = window.location.href.split("?")[1].split("=")[1];
- pageCurrent = pageCurrent.replace("#","");
+ pageCurrent = window.location.href.split("/").pop();
switch(pageCurrent) {
case "network_conf":
getAllInterfaces();
setupTabs();
setupBtns();
+ break;
+ case "hostapd_conf":
+ loadChannel();
+ setHardwareModeTooltip();
+ break;
+ case "dhcpd_conf":
+ loadInterfaceDHCPSelect();
break;
}
}
@@ -187,42 +200,322 @@ function loadWifiStations(refresh) {
.load('ajax/networking/wifi_stations.php'+qs, complete);
};
}
-
$(".js-reload-wifi-stations").on("click", loadWifiStations(true));
+/*
+Populates the DHCP server form fields
+Option toggles are set dynamically depending on the loaded configuration
+*/
+function loadInterfaceDHCPSelect() {
+ var strInterface = $('#cbxdhcpiface').val();
+ $.get('ajax/networking/get_netcfg.php?iface='+strInterface,function(data){
+ jsonData = JSON.parse(data);
+ $('#dhcp-iface')[0].checked = jsonData.DHCPEnabled;
+ $('#txtipaddress').val(jsonData.StaticIP);
+ $('#txtsubnetmask').val(jsonData.SubnetMask);
+ $('#txtgateway').val(jsonData.StaticRouters);
+ $('#chkfallback')[0].checked = jsonData.FallbackEnabled;
+ $('#default-route').prop('checked', jsonData.DefaultRoute);
+ $('#txtrangestart').val(jsonData.RangeStart);
+ $('#txtrangeend').val(jsonData.RangeEnd);
+ $('#txtrangeleasetime').val(jsonData.leaseTime);
+ $('#txtdns1').val(jsonData.DNS1);
+ $('#txtdns2').val(jsonData.DNS2);
+ $('#cbxrangeleasetimeunits').val(jsonData.leaseTimeInterval);
+ $('#no-resolv')[0].checked = jsonData.upstreamServersEnabled;
+ $('#cbxdhcpupstreamserver').val(jsonData.upstreamServers[0]);
+ $('#txtmetric').val(jsonData.Metric);
+
+ if (jsonData.StaticIP !== null && jsonData.StaticIP !== '' && !jsonData.FallbackEnabled) {
+ $('#chkstatic').closest('.btn').button('toggle');
+ $('#chkstatic').closest('.btn').button('toggle').blur();
+ $('#chkstatic').blur();
+ $('#chkfallback').prop('disabled', true);
+ } else {
+ $('#chkdhcp').closest('.btn').button('toggle');
+ $('#chkdhcp').closest('.btn').button('toggle').blur();
+ $('#chkdhcp').blur();
+ $('#chkfallback').prop('disabled', false);
+ }
+ if (jsonData.FallbackEnabled || $('#chkdhcp').is(':checked')) {
+ $('#dhcp-iface').prop('disabled', true);
+ }
+ });
+}
+
+function setDHCPToggles(state) {
+ if ($('#chkfallback').is(':checked') && state) {
+ $('#chkfallback').prop('checked', state);
+ }
+ if ($('#dhcp-iface').is(':checked') && !state) {
+ $('#dhcp-iface').prop('checked', state);
+ }
+
+ $('#chkfallback').prop('disabled', state);
+ $('#dhcp-iface').prop('disabled', !state);
+ //$('#dhcp-iface').prop('checked', state);
+}
+
+function loadChannel() {
+ $.get('ajax/networking/get_channel.php',function(data){
+ jsonData = JSON.parse(data);
+ loadChannelSelect(jsonData);
+ });
+}
+
+$('#hostapdModal').on('shown.bs.modal', function (e) {
+ var seconds = 9;
+ var countDown = setInterval(function(){
+ if(seconds <= 0){
+ clearInterval(countDown);
+ }
+ var pct = Math.floor(100-(seconds*100/9));
+ document.getElementsByClassName('progress-bar').item(0).setAttribute('style','width:'+Number(pct)+'%');
+ seconds --;
+ }, 1000);
+});
+
+$('#configureClientModal').on('shown.bs.modal', function (e) {
+});
+
+$('#ovpn-confirm-delete').on('click', '.btn-delete', function (e) {
+ var cfg_id = $(this).data('recordId');
+ $.post('ajax/openvpn/del_ovpncfg.php',{'cfg_id':cfg_id},function(data){
+ jsonData = JSON.parse(data);
+ $("#ovpn-confirm-delete").modal('hide');
+ var row = $(document.getElementById("openvpn-client-row-" + cfg_id));
+ row.fadeOut( "slow", function() {
+ row.remove();
+ });
+ });
+});
+
+$('#ovpn-confirm-delete').on('show.bs.modal', function (e) {
+ var data = $(e.relatedTarget).data();
+ $('.btn-delete', this).data('recordId', data.recordId);
+});
+
+$('#ovpn-confirm-activate').on('click', '.btn-activate', function (e) {
+ var cfg_id = $(this).data('record-id');
+ $.post('ajax/openvpn/activate_ovpncfg.php',{'cfg_id':cfg_id},function(data){
+ jsonData = JSON.parse(data);
+ $("#ovpn-confirm-activate").modal('hide');
+ setTimeout(function(){
+ window.location.reload();
+ },300);
+ });
+});
+
+$('#ovpn-confirm-activate').on('shown.bs.modal', function (e) {
+ var data = $(e.relatedTarget).data();
+ $('.btn-activate', this).data('recordId', data.recordId);
+});
+
+$('#ovpn-userpw,#ovpn-certs').on('click', function (e) {
+ if (this.id == 'ovpn-userpw') {
+ $('#PanelCerts').hide();
+ $('#PanelUserPW').show();
+ } else if (this.id == 'ovpn-certs') {
+ $('#PanelUserPW').hide();
+ $('#PanelCerts').show();
+ }
+});
+
+$('#js-system-reset-confirm').on('click', function (e) {
+ var progressText = $('#js-system-reset-confirm').attr('data-message');
+ var successHtml = $('#system-reset-message').attr('data-message');
+ var closeHtml = $('#js-system-reset-cancel').attr('data-message');
+ var csrfToken = $('meta[name=csrf_token]').attr('content');
+ var progressHtml = $('
').text(progressText).html() + '
';
+ $('#system-reset-message').html(progressHtml);
+ $.post('ajax/networking/do_sys_reset.php?',{'csrf_token':csrfToken},function(data){
+ setTimeout(function(){
+ jsonData = JSON.parse(data);
+ if(jsonData['return'] == 0) {
+ $('#system-reset-message').text(successHtml);
+ } else {
+ $('#system-reset-message').text('Error occured: '+ jsonData['return']);
+ }
+ $("#js-system-reset-confirm").hide();
+ $("#js-system-reset-cancel").text(closeHtml);
+ },750);
+ });
+});
+
+$(document).ready(function(){
+ $("#PanelManual").hide();
+});
+
+$('#wg-upload,#wg-manual').on('click', function (e) {
+ if (this.id == 'wg-upload') {
+ $('#PanelManual').hide();
+ $('#PanelUpload').show();
+ } else if (this.id == 'wg-manual') {
+ $('#PanelUpload').hide();
+ $('#PanelManual').show();
+ }
+});
+
+// Add the following code if you want the name of the file appear on select
+$(".custom-file-input").on("change", function() {
+ var fileName = $(this).val().split("\\").pop();
+ $(this).siblings(".custom-file-label").addClass("selected").html(fileName);
+});
+
+/*
+Sets the wirelss channel select options based on hw_mode and country_code.
+
+Methodology: In North America up to channel 11 is the maximum allowed WiFi 2.4Ghz channel,
+except for the US that allows channel 12 & 13 in low power mode with additional restrictions.
+Canada allows channel 12 in low power mode. Because it's unsure if low powered mode can be
+supported the channels are not selectable for those countries. Also Uzbekistan and Colombia
+allow up to channel 11 as maximum channel on the 2.4Ghz WiFi band.
+Source: https://en.wikipedia.org/wiki/List_of_WLAN_channels
+Additional: https://git.kernel.org/pub/scm/linux/kernel/git/sforshee/wireless-regdb.git
+*/
+function loadChannelSelect(selected) {
+ // Fetch wireless regulatory data
+ $.getJSON("config/wireless.json", function(json) {
+ var hw_mode = $('#cbxhwmode').val();
+ var country_code = $('#cbxcountries').val();
+ var channel_select = $('#cbxchannel');
+ var data = json["wireless_regdb"];
+ var selectablechannels = Array.range(1,14);
+
+ // Assign array of countries to valid frequencies (channels)
+ var countries_2_4Ghz_max11ch = data["2_4GHz_max11ch"].countries;
+ var countries_2_4Ghz_max14ch = data["2_4GHz_max14ch"].countries;
+ var countries_5Ghz_max48ch = data["5Ghz_max48ch"].countries;
+
+ // Map selected hw_mode and country to determine channel list
+ if (hw_mode === 'a') {
+ selectablechannels = data["5Ghz_max48ch"].channels;
+ } else if (($.inArray(country_code, countries_2_4Ghz_max11ch) !== -1) && (hw_mode !== 'ac') ) {
+ selectablechannels = data["2_4GHz_max11ch"].channels;
+ } else if (($.inArray(country_code, countries_2_4Ghz_max14ch) !== -1) && (hw_mode === 'b')) {
+ selectablechannels = data["2_4GHz_max14ch"].channels;
+ } else if (($.inArray(country_code, countries_5Ghz_max48ch) !== -1) && (hw_mode === 'ac')) {
+ selectablechannels = data["5Ghz_max48ch"].channels;
+ }
+
+ // Set channel select with available values
+ selected = (typeof selected === 'undefined') ? selectablechannels[0] : selected;
+ channel_select.empty();
+ $.each(selectablechannels, function(key,value) {
+ channel_select.append($("
").attr("value", value).text(value));
+ });
+ channel_select.val(selected);
+ });
+}
+
+/* Sets hardware mode tooltip text for selected interface.
+ */
+function setHardwareModeTooltip() {
+ var iface = $('#cbxinterface').val();
+ var hwmodeText = '';
+ // Explanatory text if 802.11ac is disabled
+ if ($('#cbxhwmode').find('option[value="ac"]').prop('disabled') == true ) {
+ var hwmodeText = $('#hwmode').attr('data-tooltip');
+ }
+ $.post('ajax/networking/get_frequencies.php?',{'interface': iface},function(data){
+ var responseText = JSON.parse(data);
+ $('#tiphwmode').attr('data-original-title', responseText + '\n' + hwmodeText );
+ });
+}
+
+/* Updates the selected blocklist
+ * Request is passed to an ajax handler to download the associated list.
+ * Interface elements are updated to indicate current progress, status.
+ */
+function updateBlocklist() {
+ var blocklist_id = $('#cbxblocklist').val();
+ if (blocklist_id == '') { return; }
+ $('#cbxblocklist-status').find('i').removeClass('fas fa-check').addClass('fas fa-cog fa-spin');
+ $('#cbxblocklist-status').removeClass('check-hidden').addClass('check-progress');
+ $.post('ajax/adblock/update_blocklist.php',{ 'blocklist_id':blocklist_id },function(data){
+ var jsonData = JSON.parse(data);
+ if (jsonData['return'] == '0') {
+ $('#cbxblocklist-status').find('i').removeClass('fas fa-cog fa-spin').addClass('fas fa-check');
+ $('#cbxblocklist-status').removeClass('check-progress').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
+ $('#'+blocklist_id).text("Just now");
+ }
+ })
+}
+
+function clearBlocklistStatus() {
+ $('#cbxblocklist-status').removeClass('check-updated').addClass('check-hidden');
+}
+
+// Handler for the wireguard generate key button
+$('.wg-keygen').click(function(){
+ var entity_pub = $(this).parent('div').prev('input[type="text"]');
+ var entity_priv = $(this).parent('div').next('input[type="hidden"]');
+ var updated = entity_pub.attr('name')+"-pubkey-status";
+ $.post('ajax/networking/get_wgkey.php',{'entity':entity_pub.attr('name') },function(data){
+ var jsonData = JSON.parse(data);
+ entity_pub.val(jsonData.pubkey);
+ $('#' + updated).removeClass('check-hidden').addClass('check-updated').delay(500).animate({ opacity: 1 }, 700);
+ })
+})
+
+// Handler for wireguard client.conf download
+$('.wg-client-dl').click(function(){
+ var req = new XMLHttpRequest();
+ var url = 'ajax/networking/get_wgcfg.php';
+ req.open('get', url, true);
+ req.responseType = 'blob';
+ req.setRequestHeader('Content-type', 'text/plain; charset=UTF-8');
+ req.onreadystatechange = function (event) {
+ if(req.readyState == 4 && req.status == 200) {
+ var blob = req.response;
+ var link=document.createElement('a');
+ link.href=window.URL.createObjectURL(blob);
+ link.download = 'client.conf';
+ link.click();
+ }
+ }
+ req.send();
+})
+
+// Event listener for Bootstrap's form validation
+window.addEventListener('load', function() {
+ // Fetch all the forms we want to apply custom Bootstrap validation styles to
+ var forms = document.getElementsByClassName('needs-validation');
+ // Loop over them and prevent submission
+ var validation = Array.prototype.filter.call(forms, function(form) {
+ form.addEventListener('submit', function(event) {
+ if (form.checkValidity() === false) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ form.classList.add('was-validated');
+ }, false);
+ });
+}, false);
+
+// Static Array method
+Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);
+
$(document).on("click", ".js-toggle-password", function(e) {
var button = $(e.target)
var field = $(button.data("target"));
+
if (field.is(":input")) {
e.preventDefault();
if (!button.data("__toggle-with-initial")) {
- button.data("__toggle-with-initial", button.text())
+ $("i", this).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
}
if (field.attr("type") === "password") {
- button.text(button.data("toggle-with"));
field.attr("type", "text");
} else {
- button.text(button.data("__toggle-with-initial"));
+ $("i", this).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
field.attr("type", "password");
}
}
});
-$(document).on("keyup", ".js-validate-psk", function(e) {
- var field = $(e.target);
- var colors = field.data("colors").split(",");
- var target = $(field.data("target"));
- if (field.val().length < 8 || field.val().length > 63) {
- field.css("backgroundColor", colors[0]);
- target.attr("disabled", true);
- } else {
- field.css("backgroundColor", colors[1]);
- target.attr("disabled", false);
- }
-});
-
$(function() {
$('#theme-select').change(function() {
var theme = themes[$( "#theme-select" ).val() ];
@@ -236,6 +529,17 @@ function set_theme(theme) {
setCookie('theme',theme,90);
}
+$(function() {
+ $('#night-mode').change(function() {
+ var state = $(this).is(':checked');
+ if (state == true && getCookie('theme') != 'lightsout.css') {
+ set_theme('lightsout.css');
+ } else {
+ set_theme('custom.php');
+ }
+ });
+});
+
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
@@ -249,10 +553,11 @@ function getCookie(cname) {
return (value != null) ? unescape(value[1]) : null;
}
+// Define themes
var themes = {
- "default": "custom.css",
+ "default": "custom.php",
"hackernews" : "hackernews.css",
- "terminal" : "terminal.css",
+ "lightsout" : "lightsout.css",
}
// Toggles the sidebar navigation.
diff --git a/app/js/dashboardchart.js b/app/js/dashboardchart.js
new file mode 100644
index 00000000..e9b039fa
--- /dev/null
+++ b/app/js/dashboardchart.js
@@ -0,0 +1,106 @@
+(function($, _t) {
+ "use strict";
+
+ /**
+ * Create a Chart.js barchart.
+ */
+ function CreateChart(ctx, labels) {
+ var barchart = new Chart(ctx,{
+ type: 'line',
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ xAxes: [{
+ scaleLabel: {
+ display: true
+ },
+ ticks: {
+ maxRotation: 0,
+ minRotation: 0
+ }
+ }],
+ yAxes: [{
+ id: 'y-axis-1',
+ type: 'linear',
+ display: true,
+ position: 'left',
+ ticks: {
+ beginAtZero: true
+ }
+ }]
+ }
+ },
+ data: {
+ labels: labels,
+ datasets: []
+ }
+ });
+ return barchart;
+ }
+
+ function ShowBandwidthChartHandler(e) {
+ // Remove hourly chartjs chart
+ $('#divDBChartBandwidthhourly').empty();
+ // Construct ajax uri for getting the proper data
+ var timeunit = 'hourly';
+ var uri = 'ajax/bandwidth/get_bandwidth.php?';
+ uri += 'inet=';
+ uri += encodeURIComponent($('#divInterface').text());
+ uri += '&tu=';
+ uri += encodeURIComponent(timeunit.substr(0, 1));
+ var datasizeunits = 'mb';
+ uri += '&dsu='+encodeURIComponent(datasizeunits);
+ // Get data for chart
+ $.ajax({
+ url: uri,
+ dataType: 'json',
+ beforeSend: function() {}
+ }).done(function(jsondata) {
+ // Map json values to label array
+ var labels = jsondata.map(function(e) {
+ return e.date;
+ });
+ // Init. chart with label series
+ var barchart = CreateChart('divDBChartBandwidth'+timeunit, labels);
+ var dataRx = jsondata.map(function(e) {
+ return e.rx;
+ });
+ var dataTx = jsondata.map(function(e) {
+ return e.tx;
+ });
+ addData(barchart, dataTx, dataRx, datasizeunits);
+ }).fail(function(xhr, textStatus) {
+ if (window.console) {
+ console.error('server error');
+ } else {
+ alert("server error");
+ }
+ });
+ }
+ /**
+ * Add data array to datasets of current chart.
+ */
+ function addData(chart, dataTx, dataRx, datasizeunits) {
+ chart.data.datasets.push({
+ label: 'Send'+' '+datasizeunits.toUpperCase(),
+ yAxisID: 'y-axis-1',
+ borderColor: 'rgba(75, 192, 192, 1)',
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
+ data: dataTx
+ });
+ chart.data.datasets.push({
+ label: 'Receive'+' '+datasizeunits.toUpperCase(),
+ yAxisID: 'y-axis-1',
+ borderColor: 'rgba(192, 192, 192, 1)',
+ backgroundColor: 'rgba(192, 192, 192, 0.2)',
+ data: dataRx
+ });
+ chart.update();
+ }
+ $(document).ready(function() {
+ ShowBandwidthChartHandler();
+ });
+
+})(jQuery, t);
+
diff --git a/app/js/huebee.js b/app/js/huebee.js
new file mode 100644
index 00000000..486d28b2
--- /dev/null
+++ b/app/js/huebee.js
@@ -0,0 +1,22 @@
+// Initialize Huebee color picker
+var elem = document.querySelector('.color-input');
+var hueb = new Huebee( elem, {
+ notation: 'hex',
+ saturations: 2,
+ customColors: [ '#d8224c', '#dd4814', '#ea0', '#19f', '#333' ],
+ className: 'light-picker',
+ hue0: 210
+});
+
+// Set custom color if defined
+var color = getCookie('color');
+if (color == null || color == '') {
+ color = '#2b8080';
+}
+hueb.setColor(color);
+
+// Change event
+hueb.on( 'change', function( color, hue, sat, lum ) {
+ setCookie('color',color,90);
+})
+
diff --git a/app/js/linkquality.js b/app/js/linkquality.js
index 6e8dd34b..40f87a7c 100644
--- a/app/js/linkquality.js
+++ b/app/js/linkquality.js
@@ -1,86 +1,86 @@
// Link quality gauge for ChartJS
-// Support for dark terminal theme
+// Support for dark theme
theme = getCookie('theme');
-if (theme == 'terminal.css') {
- var bgColor1 = '#000';
- var bgColor2 = '#000';
- var borderColor = 'rgba(46, 230, 0, 1)';
- var labelColor = 'rgba(46, 230, 0, 1)';
+if (theme == 'lightsout.css') {
+ var bgColor1 = '#141414';
+ var bgColor2 = '#141414';
+ var borderColor = 'rgba(37, 153, 63, 1)';
+ var labelColor = 'rgba(37, 153, 63, 1)';
} else {
- var bgColor1 = '#d4edda';
- var bgColor2 = '#eaecf4';
- var borderColor = 'rgba(147, 210, 162, 1)';
- var labelColor = 'rgba(130, 130, 130, 1)';
+ var bgColor1 = '#d4edda';
+ var bgColor2 = '#eaecf4';
+ var borderColor = 'rgba(147, 210, 162, 1)';
+ var labelColor = 'rgba(130, 130, 130, 1)';
}
let data1 = {
- datasets: [{
- data: [linkQ, 100-linkQ],
- backgroundColor: [bgColor1, bgColor2],
- borderColor: borderColor,
- }],
+ datasets: [{
+ data: [linkQ, 100-linkQ],
+ backgroundColor: [bgColor1, bgColor2],
+ borderColor: borderColor,
+ }],
};
let config = {
- type: 'doughnut',
- data: data1,
- options: {
- aspectRatio: 2,
- responsive: true,
- tooltips: {enabled: false},
- hover: {mode: null},
- legend: {
- display: false,
- },
- rotation: (2/3)*Math.PI,//2+(1/3),
- circumference: (1+(2/3)) * Math.PI, // * Math.PI,
- cutoutPercentage: 80,
- animation: {
- animateScale: false,
- animateRotate: true
- },
- tooltips: {
- enabled: false
- }
+ type: 'doughnut',
+ data: data1,
+ options: {
+ aspectRatio: 2,
+ responsive: true,
+ maintainAspectRatio: false,
+ tooltips: {enabled: false},
+ hover: {mode: null},
+ legend: {
+ display: false,
},
- centerText: {
- display: true,
- text: linkQ + "%"
+ rotation: (2/3)*Math.PI,//2+(1/3),
+ circumference: (1+(2/3)) * Math.PI, // * Math.PI,
+ cutoutPercentage: 80,
+ animation: {
+ animateScale: false,
+ animateRotate: true
+ },
+ tooltips: {
+ enabled: false
}
-};
-
-Chart.Chart.pluginService.register({
+ },
+ centerText: {
+ display: true,
+ text: linkQ + "%"
+ },
+ plugins: [{
beforeDraw: function(chart) {
- if (chart.config.centerText.display !== null &&
- typeof chart.config.centerText.display !== 'undefined' &&
- chart.config.centerText.display) {
- drawLinkQ(chart);
- }
+ if (chart.config.centerText.display !== null &&
+ typeof chart.config.centerText.display !== 'undefined' &&
+ chart.config.centerText.display) {
+ drawLinkQ(chart);
+ }
}
-});
+ }]
+};
function drawLinkQ(chart) {
- let width = chart.chart.width;
- let height = chart.chart.height;
- let ctx = chart.chart.ctx;
+ let width = chart.chart.width;
+ let height = chart.chart.height;
+ let ctx = chart.chart.ctx;
- ctx.restore();
- let fontSize = (height / 100).toFixed(2);
- ctx.font = fontSize + "em sans-serif";
- ctx.fillStyle = labelColor;
- ctx.textBaseline = "middle";
+ ctx.restore();
+ let fontSize = (height / 100).toFixed(2);
+ ctx.font = fontSize + "em sans-serif";
+ ctx.fillStyle = labelColor;
+ ctx.textBaseline = "middle";
- let text = chart.config.centerText.text;
- let textX = Math.round((width - ctx.measureText(text).width) * 0.5);
- let textY = height / 2;
- ctx.fillText(text, textX, textY);
- ctx.save();
+ let text = chart.config.centerText.text;
+ let textX = Math.round((width - ctx.measureText(text).width) * 0.5);
+ let textY = height / 2;
+ ctx.fillText(text, textX, textY);
+ ctx.save();
}
window.onload = function() {
- let ctx = document.getElementById("canvas").getContext("2d");
- window.myDoughnut = new Chart(ctx, config);
+ let ctx = document.getElementById("divChartLinkQ").getContext("2d");
+ var chart = new Chart(ctx, config);
};
diff --git a/app/lib/Parsedown.php b/app/lib/Parsedown.php
new file mode 100644
index 00000000..ae0cbdec
--- /dev/null
+++ b/app/lib/Parsedown.php
@@ -0,0 +1,1994 @@
+textElements($text);
+
+ # convert to markup
+ $markup = $this->elements($Elements);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ protected function textElements($text)
+ {
+ # make sure no definitions are set
+ $this->DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ return $this->linesElements($lines);
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ function setSafeMode($safeMode)
+ {
+ $this->safeMode = (bool) $safeMode;
+
+ return $this;
+ }
+
+ protected $safeMode;
+
+ function setStrictMode($strictMode)
+ {
+ $this->strictMode = (bool) $strictMode;
+
+ return $this;
+ }
+
+ protected $strictMode;
+
+ protected $safeLinksWhitelist = array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'ftps://',
+ 'mailto:',
+ 'tel:',
+ 'data:image/png;base64,',
+ 'data:image/gif;base64,',
+ 'data:image/jpeg;base64,',
+ 'irc:',
+ 'ircs:',
+ 'git:',
+ 'ssh:',
+ 'news:',
+ 'steam:',
+ );
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ return $this->elements($this->linesElements($lines));
+ }
+
+ protected function linesElements(array $lines)
+ {
+ $Elements = array();
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
+ ? $CurrentBlock['interrupted'] + 1 : 1
+ );
+ }
+
+ continue;
+ }
+
+ while (($beforeTab = strstr($line, "\t", true)) !== false)
+ {
+ $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
+
+ $line = $beforeTab
+ . str_repeat(' ', $shortage)
+ . substr($line, strlen($beforeTab) + 1)
+ ;
+ }
+
+ $indent = strspn($line, ' ');
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
+ $Block = $this->$methodName($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+ $CurrentBlock = $this->$methodName($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ if (isset($CurrentBlock))
+ {
+ $Elements[] = $this->extractElement($CurrentBlock);
+ }
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
+ {
+ $Block = $this->paragraphContinue($Line, $CurrentBlock);
+ }
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+ }
+ else
+ {
+ if (isset($CurrentBlock))
+ {
+ $Elements[] = $this->extractElement($CurrentBlock);
+ }
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+ $CurrentBlock = $this->$methodName($CurrentBlock);
+ }
+
+ # ~
+
+ if (isset($CurrentBlock))
+ {
+ $Elements[] = $this->extractElement($CurrentBlock);
+ }
+
+ # ~
+
+ return $Elements;
+ }
+
+ protected function extractElement(array $Component)
+ {
+ if ( ! isset($Component['element']))
+ {
+ if (isset($Component['markup']))
+ {
+ $Component['element'] = array('rawHtml' => $Component['markup']);
+ }
+ elseif (isset($Component['hidden']))
+ {
+ $Component['element'] = array();
+ }
+ }
+
+ return $Component['element'];
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block' . $Type . 'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block' . $Type . 'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['element']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['element']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (strpos($Line['text'], '') !== false)
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['element']['rawHtml'] .= "\n" . $Line['body'];
+
+ if (strpos($Line['text'], '-->') !== false)
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ $marker = $Line['text'][0];
+
+ $openerLength = strspn($Line['text'], $marker);
+
+ if ($openerLength < 3)
+ {
+ return;
+ }
+
+ $infostring = trim(substr($Line['text'], $openerLength), "\t ");
+
+ if (strpos($infostring, '`') !== false)
+ {
+ return;
+ }
+
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if ($infostring !== '')
+ {
+ /**
+ * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
+ * Every HTML element may have a class attribute specified.
+ * The attribute, if specified, must have a value that is a set
+ * of space-separated tokens representing the various classes
+ * that the element belongs to.
+ * [...]
+ * The space characters, for the purposes of this specification,
+ * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
+ * U+000D CARRIAGE RETURN (CR).
+ */
+ $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
+
+ $Element['attributes'] = array('class' => "language-$language");
+ }
+
+ $Block = array(
+ 'char' => $marker,
+ 'openerLength' => $openerLength,
+ 'element' => array(
+ 'name' => 'pre',
+ 'element' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+ unset($Block['interrupted']);
+ }
+
+ if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
+ and chop(substr($Line['text'], $len), ' ') === ''
+ ) {
+ $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['element']['text'] .= "\n" . $Line['body'];
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ $level = strspn($Line['text'], '#');
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '#');
+
+ if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
+ {
+ return;
+ }
+
+ $text = trim($text, ' ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . $level,
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $text,
+ 'destination' => 'elements',
+ )
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # List
+
+ protected function blockList($Line, array $CurrentBlock = null)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
+
+ if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
+ {
+ $contentIndent = strlen($matches[2]);
+
+ if ($contentIndent >= 5)
+ {
+ $contentIndent -= 1;
+ $matches[1] = substr($matches[1], 0, -$contentIndent);
+ $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
+ }
+ elseif ($contentIndent === 0)
+ {
+ $matches[1] .= ' ';
+ }
+
+ $markerWithoutWhitespace = strstr($matches[1], ' ', true);
+
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'data' => array(
+ 'type' => $name,
+ 'marker' => $matches[1],
+ 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
+ ),
+ 'element' => array(
+ 'name' => $name,
+ 'elements' => array(),
+ ),
+ );
+ $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
+
+ if ($name === 'ol')
+ {
+ $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
+
+ if ($listStart !== '1')
+ {
+ if (
+ isset($CurrentBlock)
+ and $CurrentBlock['type'] === 'Paragraph'
+ and ! isset($CurrentBlock['interrupted'])
+ ) {
+ return;
+ }
+
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => array(
+ 'function' => 'li',
+ 'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
+ 'destination' => 'elements'
+ )
+ );
+
+ $Block['element']['elements'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
+ {
+ return null;
+ }
+
+ $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
+
+ if ($Line['indent'] < $requiredIndent
+ and (
+ (
+ $Block['data']['type'] === 'ol'
+ and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+ ) or (
+ $Block['data']['type'] === 'ul'
+ and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+ )
+ )
+ ) {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['handler']['argument'] []= '';
+
+ $Block['loose'] = true;
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['indent'] = $Line['indent'];
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => array(
+ 'function' => 'li',
+ 'argument' => array($text),
+ 'destination' => 'elements'
+ )
+ );
+
+ $Block['element']['elements'] []= & $Block['li'];
+
+ return $Block;
+ }
+ elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
+ {
+ return null;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ($Line['indent'] >= $requiredIndent)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['handler']['argument'] []= '';
+
+ $Block['loose'] = true;
+
+ unset($Block['interrupted']);
+ }
+
+ $text = substr($Line['body'], $requiredIndent);
+
+ $Block['li']['handler']['argument'] []= $text;
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
+
+ $Block['li']['handler']['argument'] []= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockListComplete(array $Block)
+ {
+ if (isset($Block['loose']))
+ {
+ foreach ($Block['element']['elements'] as &$li)
+ {
+ if (end($li['handler']['argument']) !== '')
+ {
+ $li['handler']['argument'] []= '';
+ }
+ }
+ }
+
+ return $Block;
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => array(
+ 'function' => 'linesElements',
+ 'argument' => (array) $matches[1],
+ 'destination' => 'elements',
+ )
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+ {
+ $Block['element']['handler']['argument'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['handler']['argument'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ $marker = $Line['text'][0];
+
+ if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'element' => array(
+ 'rawHtml' => $Line['text'],
+ 'autobreak' => true,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ $Block['element']['rawHtml'] .= "\n" . $Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (strpos($Line['text'], ']') !== false
+ and preg_match('/^\[(.+?)\]:[ ]*+(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
+ ) {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => isset($matches[3]) ? $matches[3] : null,
+ );
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'element' => array(),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (
+ strpos($Block['element']['handler']['argument'], '|') === false
+ and strpos($Line['text'], '|') === false
+ and strpos($Line['text'], ':') === false
+ or strpos($Block['element']['handler']['argument'], "\n") !== false
+ ) {
+ return;
+ }
+
+ if (chop($Line['text'], ' -:|') !== '')
+ {
+ return;
+ }
+
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ return;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['handler']['argument'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ if (count($headerCells) !== count($alignments))
+ {
+ return;
+ }
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $headerCell,
+ 'destination' => 'elements',
+ )
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => "text-align: $alignment;",
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'elements' => array(),
+ ),
+ );
+
+ $Block['element']['elements'] []= array(
+ 'name' => 'thead',
+ );
+
+ $Block['element']['elements'] []= array(
+ 'name' => 'tbody',
+ 'elements' => array(),
+ );
+
+ $Block['element']['elements'][0]['elements'] []= array(
+ 'name' => 'tr',
+ 'elements' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
+
+ $cells = array_slice($matches[0], 0, count($Block['alignments']));
+
+ foreach ($cells as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $cell,
+ 'destination' => 'elements',
+ )
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'elements' => $Elements,
+ );
+
+ $Block['element']['elements'][1]['elements'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ return array(
+ 'type' => 'Paragraph',
+ 'element' => array(
+ 'name' => 'p',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $Line['text'],
+ 'destination' => 'elements',
+ ),
+ ),
+ );
+ }
+
+ protected function paragraphContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ $Block['element']['handler']['argument'] .= "\n".$Line['text'];
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!*_&[:<`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text, $nonNestables = array())
+ {
+ return $this->elements($this->lineElements($text, $nonNestables));
+ }
+
+ protected function lineElements($text, $nonNestables = array())
+ {
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ $Elements = array();
+
+ $nonNestables = (empty($nonNestables)
+ ? array()
+ : array_combine($nonNestables, $nonNestables)
+ );
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strlen($text) - strlen($excerpt);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ # check to see if the current inline type is nestable in the current context
+
+ if (isset($nonNestables[$inlineType]))
+ {
+ continue;
+ }
+
+ $Inline = $this->{"inline$inlineType"}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # cause the new element to 'inherit' our non nestables
+
+
+ $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
+ ? array_merge($Inline['element']['nonNestables'], $nonNestables)
+ : $nonNestables
+ ;
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $InlineText = $this->inlineText($unmarkedText);
+ $Elements[] = $InlineText['element'];
+
+ # compile the inline
+ $Elements[] = $this->extractElement($Inline);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $InlineText = $this->inlineText($unmarkedText);
+ $Elements[] = $InlineText['element'];
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $InlineText = $this->inlineText($text);
+ $Elements[] = $InlineText['element'];
+
+ foreach ($Elements as &$Element)
+ {
+ if ( ! isset($Element['autobreak']))
+ {
+ $Element['autobreak'] = false;
+ }
+ }
+
+ return $Elements;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineText($text)
+ {
+ $Inline = array(
+ 'extent' => strlen($text),
+ 'element' => array(),
+ );
+
+ $Inline['element']['elements'] = self::pregReplaceElements(
+ $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
+ array(
+ array('name' => 'br'),
+ array('text' => "\n"),
+ ),
+ $text
+ );
+
+ return $Inline;
+ }
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
+
+ $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
+ . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
+
+ if (strpos($Excerpt['text'], '>') !== false
+ and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
+ ){
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = "mailto:$url";
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $matches[1],
+ 'destination' => 'elements',
+ )
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'element' => array('rawHtml' => $Excerpt['text'][1]),
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['handler']['argument'],
+ ),
+ 'autobreak' => true,
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => null,
+ 'destination' => 'elements',
+ ),
+ 'nonNestables' => array('Url', 'Link'),
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+ {
+ $Element['handler']['argument'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['handler']['argument']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'element' => array('rawHtml' => $matches[0]),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'element' => array('rawHtml' => $matches[0]),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'element' => array('rawHtml' => $matches[0]),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
+ and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
+ ) {
+ return array(
+ 'element' => array('rawHtml' => '&' . $matches[1] . ';'),
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ return;
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'handler' => array(
+ 'function' => 'lineElements',
+ 'argument' => $matches[1],
+ 'destination' => 'elements',
+ )
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (strpos($Excerpt['context'], 'http') !== false
+ and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
+ ) {
+ $url = $matches[0][0];
+
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ $Inline = $this->inlineText($text);
+ return $this->element($Inline['element']);
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function handle(array $Element)
+ {
+ if (isset($Element['handler']))
+ {
+ if (!isset($Element['nonNestables']))
+ {
+ $Element['nonNestables'] = array();
+ }
+
+ if (is_string($Element['handler']))
+ {
+ $function = $Element['handler'];
+ $argument = $Element['text'];
+ unset($Element['text']);
+ $destination = 'rawHtml';
+ }
+ else
+ {
+ $function = $Element['handler']['function'];
+ $argument = $Element['handler']['argument'];
+ $destination = $Element['handler']['destination'];
+ }
+
+ $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
+
+ if ($destination === 'handler')
+ {
+ $Element = $this->handle($Element);
+ }
+
+ unset($Element['handler']);
+ }
+
+ return $Element;
+ }
+
+ protected function handleElementRecursive(array $Element)
+ {
+ return $this->elementApplyRecursive(array($this, 'handle'), $Element);
+ }
+
+ protected function handleElementsRecursive(array $Elements)
+ {
+ return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
+ }
+
+ protected function elementApplyRecursive($closure, array $Element)
+ {
+ $Element = call_user_func($closure, $Element);
+
+ if (isset($Element['elements']))
+ {
+ $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
+ }
+ elseif (isset($Element['element']))
+ {
+ $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
+ }
+
+ return $Element;
+ }
+
+ protected function elementApplyRecursiveDepthFirst($closure, array $Element)
+ {
+ if (isset($Element['elements']))
+ {
+ $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
+ }
+ elseif (isset($Element['element']))
+ {
+ $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
+ }
+
+ $Element = call_user_func($closure, $Element);
+
+ return $Element;
+ }
+
+ protected function elementsApplyRecursive($closure, array $Elements)
+ {
+ foreach ($Elements as &$Element)
+ {
+ $Element = $this->elementApplyRecursive($closure, $Element);
+ }
+
+ return $Elements;
+ }
+
+ protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
+ {
+ foreach ($Elements as &$Element)
+ {
+ $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
+ }
+
+ return $Elements;
+ }
+
+ protected function element(array $Element)
+ {
+ if ($this->safeMode)
+ {
+ $Element = $this->sanitiseElement($Element);
+ }
+
+ # identity map if element has no handler
+ $Element = $this->handle($Element);
+
+ $hasName = isset($Element['name']);
+
+ $markup = '';
+
+ if ($hasName)
+ {
+ $markup .= '<' . $Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= " $name=\"".self::escape($value).'"';
+ }
+ }
+ }
+
+ $permitRawHtml = false;
+
+ if (isset($Element['text']))
+ {
+ $text = $Element['text'];
+ }
+ // very strongly consider an alternative if you're writing an
+ // extension
+ elseif (isset($Element['rawHtml']))
+ {
+ $text = $Element['rawHtml'];
+
+ $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
+ $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
+ }
+
+ $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
+
+ if ($hasContent)
+ {
+ $markup .= $hasName ? '>' : '';
+
+ if (isset($Element['elements']))
+ {
+ $markup .= $this->elements($Element['elements']);
+ }
+ elseif (isset($Element['element']))
+ {
+ $markup .= $this->element($Element['element']);
+ }
+ else
+ {
+ if (!$permitRawHtml)
+ {
+ $markup .= self::escape($text, true);
+ }
+ else
+ {
+ $markup .= $text;
+ }
+ }
+
+ $markup .= $hasName ? '' . $Element['name'] . '>' : '';
+ }
+ elseif ($hasName)
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ $autoBreak = true;
+
+ foreach ($Elements as $Element)
+ {
+ if (empty($Element))
+ {
+ continue;
+ }
+
+ $autoBreakNext = (isset($Element['autobreak'])
+ ? $Element['autobreak'] : isset($Element['name'])
+ );
+ // (autobreak === false) covers both sides of an element
+ $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
+
+ $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
+ $autoBreak = $autoBreakNext;
+ }
+
+ $markup .= $autoBreak ? "\n" : '';
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $Elements = $this->linesElements($lines);
+
+ if ( ! in_array('', $lines)
+ and isset($Elements[0]) and isset($Elements[0]['name'])
+ and $Elements[0]['name'] === 'p'
+ ) {
+ unset($Elements[0]['name']);
+ }
+
+ return $Elements;
+ }
+
+ #
+ # AST Convenience
+ #
+
+ /**
+ * Replace occurrences $regexp with $Elements in $text. Return an array of
+ * elements representing the replacement.
+ */
+ protected static function pregReplaceElements($regexp, $Elements, $text)
+ {
+ $newElements = array();
+
+ while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
+ {
+ $offset = $matches[0][1];
+ $before = substr($text, 0, $offset);
+ $after = substr($text, $offset + strlen($matches[0][0]));
+
+ $newElements[] = array('text' => $before);
+
+ foreach ($Elements as $Element)
+ {
+ $newElements[] = $Element;
+ }
+
+ $text = $after;
+ }
+
+ $newElements[] = array('text' => $text);
+
+ return $newElements;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ protected function sanitiseElement(array $Element)
+ {
+ static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+ static $safeUrlNameToAtt = array(
+ 'a' => 'href',
+ 'img' => 'src',
+ );
+
+ if ( ! isset($Element['name']))
+ {
+ unset($Element['attributes']);
+ return $Element;
+ }
+
+ if (isset($safeUrlNameToAtt[$Element['name']]))
+ {
+ $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+ }
+
+ if ( ! empty($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $att => $val)
+ {
+ # filter out badly parsed attribute
+ if ( ! preg_match($goodAttribute, $att))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ # dump onevent attribute
+ elseif (self::striAtStart($att, 'on'))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ }
+ }
+
+ return $Element;
+ }
+
+ protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+ {
+ foreach ($this->safeLinksWhitelist as $scheme)
+ {
+ if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+ {
+ return $Element;
+ }
+ }
+
+ $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+ return $Element;
+ }
+
+ #
+ # Static Methods
+ #
+
+ protected static function escape($text, $allowQuotes = false)
+ {
+ return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+ }
+
+ protected static function striAtStart($string, $needle)
+ {
+ $len = strlen($needle);
+
+ if ($len > strlen($string))
+ {
+ return false;
+ }
+ else
+ {
+ return strtolower(substr($string, 0, $len)) === strtolower($needle);
+ }
+ }
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
+ );
+}
diff --git a/app/lib/system.php b/app/lib/system.php
index e7007d0a..22f7255a 100644
--- a/app/lib/system.php
+++ b/app/lib/system.php
@@ -1,49 +1,90 @@
+ * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
+ */
- public function uptime() {
- $uparray = explode(" ", exec("cat /proc/uptime"));
- $seconds = round($uparray[0], 0);
- $minutes = $seconds / 60;
- $hours = $minutes / 60;
- $days = floor($hours / 24);
- $hours = floor($hours - ($days * 24));
- $minutes = floor($minutes - ($days * 24 * 60) - ($hours * 60));
- $uptime= '';
- if ($days != 0) {
- $uptime .= $days . ' day' . (($days > 1)? 's ':' ');
- }
- if ($hours != 0) {
- $uptime .= $hours . ' hour' . (($hours > 1)? 's ':' ');
- }
- if ($minutes != 0) {
- $uptime .= $minutes . ' minute' . (($minutes > 1)? 's ':' ');
+namespace RaspAP\System;
+
+class Sysinfo
+{
+ public function hostname()
+ {
+ return shell_exec("hostname -f");
}
- return $uptime;
- }
+ public function uptime()
+ {
+ $uparray = explode(" ", exec("cat /proc/uptime"));
+ $seconds = round($uparray[0], 0);
+ $minutes = $seconds / 60;
+ $hours = $minutes / 60;
+ $days = floor($hours / 24);
+ $hours = floor($hours - ($days * 24));
+ $minutes = floor($minutes - ($days * 24 * 60) - ($hours * 60));
+ $uptime= '';
+ if ($days != 0) {
+ $uptime .= $days . ' day' . (($days > 1)? 's ':' ');
+ }
+ if ($hours != 0) {
+ $uptime .= $hours . ' hour' . (($hours > 1)? 's ':' ');
+ }
+ if ($minutes != 0) {
+ $uptime .= $minutes . ' minute' . (($minutes > 1)? 's ':' ');
+ }
- public function usedMemory() {
- $used = shell_exec("free -m | awk '/Mem:/ { total=$2 ; used=$3 } END { print used/total*100}'");
- return floor($used);
- }
+ return $uptime;
+ }
- public function processorCount() {
- $procs = shell_exec("nproc --all");
- return intval($proc);
- }
+ public function usedMemory()
+ {
+ $used = shell_exec("free -m | awk 'NR==2{ total=$2 ; used=$3 } END { print used/total*100}'");
+ return floor($used);
+ }
- public function loadAvg1Min() {
- $load = exec("awk '{print $1}' /proc/loadavg");
- return floatval($load);
- }
+ public function processorCount()
+ {
+ $procs = shell_exec("nproc --all");
+ return intval($procs);
+ }
- public function systemLoadPercentage() {
- return intval(($this->loadAvg1Min() * 100) / $this->processorCount());
- }
+ public function loadAvg1Min()
+ {
+ $load = exec("awk '{print $1}' /proc/loadavg");
+ return floatval($load);
+ }
+
+ public function systemLoadPercentage()
+ {
+ return intval(($this->loadAvg1Min() * 100) / $this->processorCount());
+ }
+
+ public function systemTemperature()
+ {
+ $cpuTemp = file_get_contents("/sys/class/thermal/thermal_zone0/temp");
+ return number_format((float)$cpuTemp/1000, 1);
+ }
+
+ public function hostapdStatus()
+ {
+ exec('pidof hostapd | wc -l', $status);
+ return $status;
+ }
+
+ public function operatingSystem()
+ {
+ $os_desc = shell_exec("lsb_release -sd");
+ return $os_desc;
+ }
+
+ public function kernelVersion()
+ {
+ $kernel = shell_exec("uname -r");
+ return $kernel;
+ }
}
diff --git a/app/lib/uploader.php b/app/lib/uploader.php
new file mode 100644
index 00000000..e15e451c
--- /dev/null
+++ b/app/lib/uploader.php
@@ -0,0 +1,505 @@
+
+ * @author Aivis Silins
+ * @link https://github.com/aivis/PHP-file-upload-class
+ * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
+ */
+
+namespace RaspAP\Uploader;
+
+class Upload
+{
+
+ /**
+ * Default directory persmissions (destination)
+ */
+ protected $default_permissions = 0750;
+
+ /**
+ * File post array
+ *
+ * @var array
+ */
+ protected $file_post = array();
+
+ /**
+ * Destination directory
+ *
+ * @var string
+ */
+ protected $destination;
+
+ /**
+ * Fileinfo
+ *
+ * @var object
+ */
+ protected $finfo;
+
+ /**
+ * Data about file
+ *
+ * @var array
+ */
+ public $file = array();
+
+ /**
+ * Max. file size
+ *
+ * @var int
+ */
+ protected $max_file_size;
+
+ /**
+ * Allowed mime types
+ *
+ * @var array
+ */
+ protected $mimes = array();
+
+ /**
+ * Temp path
+ *
+ * @var string
+ */
+ protected $tmp_name;
+
+ /**
+ * Validation errors
+ *
+ * @var array
+ */
+ protected $validation_errors = array();
+
+ /**
+ * Filename (new)
+ *
+ * @var string
+ */
+ protected $filename;
+
+ /**
+ * Internal callbacks (filesize check, mime, etc)
+ *
+ * @var array
+ */
+ private $callbacks = array();
+
+ /**
+ * Root dir
+ *
+ * @var string
+ */
+ protected $root;
+
+ /**
+ * Return upload object
+ *
+ * $destination = 'path/to/file/destination/';
+ *
+ * @param string $destination
+ * @param string $root
+ * @return Upload
+ */
+ public static function factory($destination, $root = false)
+ {
+ return new Upload($destination, $root);
+ }
+
+ /**
+ * Define root constant and set & create destination path
+ *
+ * @param string $destination
+ * @param string $root
+ */
+ public function __construct($destination, $root = false)
+ {
+ if ($root) {
+ $this->root = $root;
+ } else {
+ $this->root = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR;
+ }
+
+ // set & create destination path
+ if (!$this->set_destination($destination)) {
+ throw new Exception('Upload: Unable to create destination. '.$this->root . $this->destination);
+ }
+ //create finfo object
+ $this->finfo = new \finfo();
+ }
+
+ /**
+ * Set target filename
+ *
+ * @param string $filename
+ */
+ public function set_filename($filename)
+ {
+ $this->filename = $filename;
+ }
+
+ /**
+ * Check & Save file
+ *
+ * Return data about current upload
+ *
+ * @return array
+ */
+ public function upload($filename = false)
+ {
+ if($filename ) {
+ $this->set_filename($filename);
+ }
+
+ $this->set_filename($filename);
+
+ if ($this->check()) {
+ $this->save();
+ }
+
+ // return state data
+ return $this->get_state();
+ }
+
+ /**
+ * Save file on server
+ * Return state data
+ *
+ * @return array
+ */
+ public function save()
+ {
+ $this->save_file();
+ return $this->get_state();
+ }
+
+ /**
+ * Validate file (execute callbacks)
+ * Returns TRUE if validation successful
+ *
+ * @return bool
+ */
+ public function check()
+ {
+ //execute callbacks (check filesize, mime, also external callbacks
+ $this->validate();
+
+ //add error messages
+ $this->file['errors'] = $this->get_errors();
+
+ //change file validation status
+ $this->file['status'] = empty($this->validation_errors);
+
+ return $this->file['status'];
+ }
+
+ /**
+ * Get current state data
+ *
+ * @return array
+ */
+ public function get_state()
+ {
+ return $this->file;
+ }
+
+ /**
+ * Save file on server
+ */
+ protected function save_file()
+ {
+ //create & set new filename
+ if(empty($this->filename)) {
+ $this->create_new_filename();
+ }
+
+ //set filename
+ $this->file['filename'] = $this->filename;
+
+ //set full path
+ $this->file['full_path'] = $this->root . $this->destination . $this->filename;
+ $this->file['path'] = $this->destination . $this->filename;
+
+ $status = move_uploaded_file($this->tmp_name, $this->file['full_path']);
+
+ //checks whether upload successful
+ if (!$status) {
+ throw new Exception('Upload: Failed to upload file.');
+ }
+
+ //done
+ $this->file['status'] = true;
+ }
+
+ /**
+ * Set data about file
+ */
+ protected function set_file_data()
+ {
+ $file_size = $this->get_file_size();
+ $this->file = array(
+ 'status' => false,
+ 'destination' => $this->destination,
+ 'size_in_bytes' => $file_size,
+ 'size_in_mb' => $this->bytes_to_mb($file_size),
+ 'mime' => $this->get_file_mime(),
+ 'filename' => $this->file_post['name'],
+ 'tmp_name' => $this->file_post['tmp_name'],
+ 'post_data' => $this->file_post,
+ );
+ }
+
+ /**
+ * Set validation error
+ *
+ * @param string $message
+ */
+ public function set_error($message)
+ {
+ $this->validation_errors[] = $message;
+ }
+
+ /**
+ * Return validation errors
+ *
+ * @return array
+ */
+ public function get_errors()
+ {
+ return $this->validation_errors;
+ }
+
+ /**
+ * Set external callback methods
+ *
+ * @param object $instance_of_callback_object
+ * @param array $callback_methods
+ */
+ public function callbacks($instance_of_callback_object, $callback_methods)
+ {
+ if (empty($instance_of_callback_object)) {
+ throw new Exception('Upload: $instance_of_callback_object cannot be empty.');
+
+ }
+
+ if (!is_array($callback_methods)) {
+ throw new Exception('Upload: $callback_methods data type need to be array.');
+ }
+
+ $this->external_callback_object = $instance_of_callback_object;
+ $this->external_callback_methods = $callback_methods;
+ }
+
+ /**
+ * Execute callbacks
+ */
+ protected function validate()
+ {
+ //get curent errors
+ $errors = $this->get_errors();
+
+ if (empty($errors)) {
+
+ //set data about current file
+ $this->set_file_data();
+
+ //execute internal callbacks
+ $this->execute_callbacks($this->callbacks, $this);
+
+ //execute external callbacks
+ $this->execute_callbacks($this->external_callback_methods, $this->external_callback_object);
+ }
+ }
+
+ /**
+ * Execute callbacks
+ */
+ protected function execute_callbacks($callbacks, $object)
+ {
+ foreach($callbacks as $method) {
+ $object->$method($this);
+
+ }
+ }
+
+ /**
+ * File mime type validation callback
+ *
+ * @param object $object
+ */
+ protected function check_mime_type($object)
+ {
+ if (!empty($object->mimes)) {
+ if (!in_array($object->file['mime'], $object->mimes)) {
+ $object->set_error('MIME type not allowed.');
+ }
+ }
+ }
+
+ /**
+ * Set allowed mime types
+ *
+ * @param array $mimes
+ */
+ public function set_allowed_mime_types($mimes)
+ {
+ $this->mimes = $mimes;
+ //if mime types is set -> set callback
+ $this->callbacks[] = 'check_mime_type';
+ }
+
+ /**
+ * File size validation callback
+ *
+ * @param object $object
+ */
+ protected function check_file_size($object)
+ {
+ if (!empty($object->max_file_size)) {
+ $file_size_in_mb = $this->bytes_to_mb($object->file['size_in_bytes']);
+ if ($object->max_file_size <= $file_size_in_mb) {
+ $object->set_error('File exceeds maximum allowed size.');
+ }
+ }
+ }
+
+ /**
+ * Set max file size
+ *
+ * @param int $size
+ */
+ public function set_max_file_size($size)
+ {
+ $this->max_file_size = $size;
+
+ //if max file size is set -> set callback
+ $this->callbacks[] = 'check_file_size';
+ }
+
+ /**
+ * Set File array to object
+ *
+ * @param array $file
+ */
+ public function file($file)
+ {
+ $this->set_file_array($file);
+ }
+
+ /**
+ * Set file array
+ *
+ * @param array $file
+ */
+ protected function set_file_array($file)
+ {
+ //checks whether file array is valid
+ if (!$this->check_file_array($file)) {
+ //file not selected or some bigger problems (broken files array)
+ $this->set_error('Please select file.');
+ }
+
+ //set file data
+ $this->file_post = $file;
+
+ //set tmp path
+ $this->tmp_name = $file['tmp_name'];
+ }
+
+ /**
+ * Checks whether Files post array is valid
+ *
+ * @return bool
+ */
+ protected function check_file_array($file)
+ {
+ return isset($file['error'])
+ && !empty($file['name'])
+ && !empty($file['type'])
+ && !empty($file['tmp_name'])
+ && !empty($file['size']);
+ }
+
+ /**
+ * Get file mime type
+ *
+ * @return string
+ */
+ protected function get_file_mime()
+ {
+ return $this->finfo->file($this->tmp_name, FILEINFO_MIME_TYPE);
+ }
+
+ /**
+ * Get file size
+ *
+ * @return int
+ */
+ protected function get_file_size()
+ {
+ return filesize($this->tmp_name);
+ }
+
+ /**
+ * Set destination path (return TRUE on success)
+ *
+ * @param string $destination
+ * @return bool
+ */
+ protected function set_destination($destination)
+ {
+ $this->destination = $destination . DIRECTORY_SEPARATOR;
+ return $this->destination_exist() ? true : $this->create_destination();
+ }
+
+ /**
+ * Checks whether destination folder exists
+ *
+ * @return bool
+ */
+ protected function destination_exist()
+ {
+ return is_writable($this->root . $this->destination);
+ }
+
+ /**
+ * Create path to destination
+ *
+ * @param string $dir
+ * @return bool
+ */
+ protected function create_destination()
+ {
+ return mkdir($this->root . $this->destination, $this->default_permissions, true);
+ }
+
+ /**
+ * Set unique filename
+ *
+ * @return string
+ */
+ protected function create_new_filename()
+ {
+ $filename = sha1(mt_rand(1, 9999) . $this->destination . uniqid()) . time();
+ $this->set_filename($filename);
+ }
+
+ /**
+ * Convert bytes to MB
+ *
+ * @param int $bytes
+ * @return int
+ */
+ protected function bytes_to_mb($bytes)
+ {
+ return round(($bytes / 1048576), 2);
+ }
+}
+
diff --git a/app/pitft/stats.py b/app/pitft/stats.py
new file mode 100644
index 00000000..ba8a3648
--- /dev/null
+++ b/app/pitft/stats.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+# Author: @billz
+# Author URI: https://github.com/billz
+# Description: RaspAP stats display for the Adafruit Mini PiTFT,
+# a 135x240 Color TFT add-on for the Raspberry Pi.
+# Based on Adafruit's rgb_display_ministats.py
+# See: https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display
+# License: MIT License
+
+import time
+import subprocess
+import digitalio
+import board
+from PIL import Image, ImageDraw, ImageFont
+import adafruit_rgb_display.st7789 as st7789
+
+# Configuration for CS and DC pins
+cs_pin = digitalio.DigitalInOut(board.CE0)
+dc_pin = digitalio.DigitalInOut(board.D25)
+reset_pin = None
+
+# Config for display baudrate (default max is 24mhz)
+BAUDRATE = 64000000
+
+# Setup SPI bus using hardware SPI
+spi = board.SPI()
+
+# Create the ST7789 display
+disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE,
+ width=135, height=240, x_offset=53, y_offset=40)
+
+# Create blank image with mode 'RGB'
+height = disp.width # swap height/width to rotate it to landscape
+width = disp.height
+image = Image.new('RGB', (width, height))
+rotation = 90
+
+# Get a drawing object and clear the image
+draw = ImageDraw.Draw(image)
+draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
+disp.image(image,rotation)
+
+# Define some constants
+padding = -2
+top = padding
+bottom = height-padding
+# Move left to right keeping track of the current x position
+x = 0
+
+# Load DejaVu TTF Font
+# Install with: sudo apt-get install ttf-dejavu
+font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24)
+
+# Turn on the backlight
+backlight = digitalio.DigitalInOut(board.D22)
+backlight.switch_to_output()
+backlight.value = True
+
+while True:
+ # Draw a black filled box to clear the image
+ draw.rectangle((0, 0, width, height), outline=0, fill=0)
+
+ # Collect basic system stats
+ cmd = "hostname -I | cut -d\' \' -f1"
+ IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8")
+
+ cmd = "pidof hostapd | wc -l | awk '{printf \"Hotspot: %s\", $1 == 1 ? \"Active\" : \"Down\"}'"
+ Hostapd = subprocess.check_output(cmd, shell=True).decode("utf-8")
+
+ cmd = "vnstat -i wlan0 | grep tx: | awk '{printf \"Data Tx: %d %s\", $5,$6}'"
+ DataTx = subprocess.check_output(cmd, shell=True).decode("utf-8")
+
+ cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
+ CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
+
+ cmd = "free -m | awk 'NR==2{printf \"Mem: %sMB %.2f%%\", $3,$3*100/$2 }'"
+ MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
+
+ cmd = "cat /sys/class/thermal/thermal_zone0/temp | awk \'{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}\'" # pylint: disable=line-too-long
+ Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")
+
+ # Write five lines of stats
+ y = top
+ draw.text((x, y), IP, font=font, fill="#ffaaaa")
+ y += font.getsize(IP)[1]
+ draw.text((x, y), Hostapd, font=font, fill="#d46a6a")
+ y += font.getsize(Hostapd)[1]
+ draw.text((x, y), DataTx, font=font, fill="#aa3939")
+ y += font.getsize(DataTx)[1]
+ draw.text((x, y), MemUsage, font=font, fill="#801515")
+ y += font.getsize(MemUsage)[1]
+ draw.text((x, y), Temp, font=font, fill="#550000")
+
+ # Display image
+ disp.image(image, rotation)
+ time.sleep(.1)
+
diff --git a/composer.json b/composer.json
new file mode 100644
index 00000000..ed4f5e8f
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "raspap/raspap-webgui",
+ "description": "Simple wireless AP setup and mangement for Debian-based devices",
+ "license": "GPL-3.0",
+ "homepage": "https://raspap.com/",
+ "keywords": ["raspberrypi", "debian", "armbian", "wifi"],
+ "type": "raspap-core",
+ "authors": [
+ {
+ "name": "RaspAP Team",
+ "email": "billzimmerman@gmail.com",
+ "homepage": "https://raspap.com/"
+ }
+ ],
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-parallel-lint": "^1.2.0",
+ "phpcompatibility/php-compatibility": "^9.3.5",
+ "squizlabs/php_codesniffer": "^3.5.5"
+ },
+ "scripts": {
+ "lint": "parallel-lint . --exclude vendor",
+ "phpcs": "phpcs -p -s --config-set installed_paths vendor/phpcompatibility/php-compatibility .",
+ "test": [
+ "composer lint",
+ "composer phpcs"
+ ]
+ }
+}
diff --git a/config/090_raspap.conf b/config/090_raspap.conf
new file mode 100644
index 00000000..f44d1a89
--- /dev/null
+++ b/config/090_raspap.conf
@@ -0,0 +1,4 @@
+# RaspAP default config
+log-facility=/var/log/dnsmasq.log
+conf-dir=/etc/dnsmasq.d
+
diff --git a/config/090_wlan0.conf b/config/090_wlan0.conf
new file mode 100644
index 00000000..026c3928
--- /dev/null
+++ b/config/090_wlan0.conf
@@ -0,0 +1,6 @@
+# RaspAP wlan0 configuration for wired (ethernet) AP mode
+interface=wlan0
+domain-needed
+dhcp-range=10.3.141.50,10.3.141.254,255.255.255.0,12h
+dhcp-option=6,9.9.9.9,1.1.1.1
+
diff --git a/config/50-raspap-router.conf b/config/50-raspap-router.conf
new file mode 100644
index 00000000..1c10d26f
--- /dev/null
+++ b/config/50-raspap-router.conf
@@ -0,0 +1,9 @@
+server.modules += (
+ "mod_rewrite",
+)
+
+$HTTP["url"] =~ "^/REPLACE_ME/(?!(dist|app|ajax|config)).*" {
+ url.rewrite-once = ( "^/REPLACE_ME/(.*?)(\?.+)?$"=>"/REPLACE_ME/index.php/$1$2" )
+ server.error-handler-404 = "/REPLACE_ME/index.php"
+}
+
diff --git a/config/blocklists.json b/config/blocklists.json
new file mode 100644
index 00000000..5c76e464
--- /dev/null
+++ b/config/blocklists.json
@@ -0,0 +1,6 @@
+{
+ "notracking/hosts-blocklist": [
+ "notracking-hostnames",
+ "notracking-domains"
+ ]
+}
diff --git a/config/client_config/70-mobile-data-sticks.rules b/config/client_config/70-mobile-data-sticks.rules
new file mode 100644
index 00000000..5ba62000
--- /dev/null
+++ b/config/client_config/70-mobile-data-sticks.rules
@@ -0,0 +1,4 @@
+# mobile data modem - ttyUSB0 device appears
+SUBSYSTEM=="tty", KERNEL=="ttyUSB0", TAG+="systemd", ENV{SYSTEMD_WANTS}="start start_ppp0_device.service"
+
+
diff --git a/config/client_config/80-raspap-net-devices.rules b/config/client_config/80-raspap-net-devices.rules
new file mode 100644
index 00000000..ddec8203
--- /dev/null
+++ b/config/client_config/80-raspap-net-devices.rules
@@ -0,0 +1,3 @@
+SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="14db", NAME="hilink%n", TAG+="systemd", ENV{SYSTEMD_WANTS}="start start_huawei_hilink@hilink%n.service"
+
+
diff --git a/config/client_config/huawei_hilink_api.sh b/config/client_config/huawei_hilink_api.sh
new file mode 100644
index 00000000..6dc439f4
--- /dev/null
+++ b/config/client_config/huawei_hilink_api.sh
@@ -0,0 +1,505 @@
+#!/bin/bash
+#
+# Huawei Hilink API
+# =================
+# - communication with Hilink devices via HTTP
+# - send a standard http request with a xml formatted string to the device (default IP 192.169.8.1)
+# - Howto:
+# o "source" this script in your own script from the command line
+# o if hilink_host ip/name differs, set "hilink_host=192.168.178.1" before calling any function
+# o if the device is locked by a password, set hilink_user="admin"; hilink_password"1234secret"
+# _login is called automatically
+# only password type 4 is supported
+# o if the SIM is requiring a PIN, set "hilink_pin=1234"
+# o connect device to network: _switchMobileData ON ( or 1 )
+# o disconnect device: _switchMobileData OFF ( or 0 )
+# o get informations about the device: _getDeviceInformation and _getStatus and _getNetProvider
+# all functions return XML formatted data in $response.
+# o _getAllInformations: returns all available informations as key/value pairs (outputs text)
+# o Check if device is connected: "if _isConnected; then .... fi"
+# o $response can be parsed by calling _valueFromResponse
+# e.g "_valueFromResponse msisdn" to get the phone number after a call to _getDeviceInformation
+#
+#
+# Usage of functions
+# - call the function with parameters (if required)
+# - return code: 0 - success; 1 - failed
+# - $status: status information (OK, ERROR)
+# - $response: xml response to be parsed for the required information
+#
+#
+# required software: curl, base64, sha256sum, sed
+#
+# ToDo: improve error handling
+#
+# zbchristian 2021
+#
+
+# Initialization procedure
+# ========================
+#
+# hilink_host=192.168.8.1 # ip address of device
+# hilink_user="admin" # user name if locked (default admin)
+# hilink_password="1234Secret" # password if locked
+# hilink_pin="1234" # PIN of SIM
+# _initHilinkAPI # initialize the API
+#
+# Termination
+# ===========
+# cleanup the API before quitting the shell
+# _closeHilinkAPI (optional: add parameter "save" to save the session/token data for subsequent calls. Valid for a few minutes.)
+#
+# BE AWARE, THAT THE API USES SOME GLOBAL VARIABLES : hilink_host, user, password, pin, response, status
+# USE THESE ONLY TO COMMUNICATE WITH THE API.
+# DO NOT USE THE VARIABLE PRE_FIX "hilink_" FOR YOUR OWN VARIABLES
+#
+
+hilink_host_default="192.168.8.1"
+hilink_save_file="/tmp/hilink_api_saved.dat"
+hilink_save_age=60
+hilink_header_file="/tmp/hilink_login_hdr.txt"
+
+# initialize
+function _initHilinkAPI() {
+ local age
+ if [ -z "$hilink_host" ]; then hilink_host=$hilink_host_default; fi
+ if ! _hostReachable; then return 1; fi
+ if [ -f $hilink_save_file ]; then # found file with saved data
+ _getSavedData
+ age=$(( $(date +%s) - $(stat $hilink_save_file -c %Y) ))
+ if [[ $age -gt $hilink_save_age ]]; then
+ rm -f $hilink_save_file
+ _logout
+ _sessToken
+ fi
+ fi
+ if [ -z "$hilink_sessID" ] || [ -z "$hilink_token" ]; then _sessToken; fi
+ _login
+ return $?
+}
+
+function _getSavedData() {
+ local dat
+ if [ -f $hilink_save_file ]; then # restore saved session data
+ dat=$(cat $hilink_save_file)
+ hilink_sessID=$(echo "$dat" | sed -nr 's/sessionid: ([a-z0-9]*)/\1/ip')
+ hilink_token=$(echo "$dat" | sed -nr 's/token: ([a-z0-9]*)/\1/ip')
+ hilink_tokenlist=( $(echo "$dat" | sed -nr 's/tokenlist: ([a-z0-9 ]*)/\1/ip') )
+ fi
+}
+
+# Cleanup
+# parameter: "save" - will store sessionid and tokens in file
+function _closeHilinkAPI() {
+ local opt
+ if [ -z "$hilink_host" ]; then hilink_host=$hilink_host_default; fi
+ if ! _hostReachable; then return 1; fi
+ rm -f $hilink_save_file
+ [ ! -z "$1" ] && opt="${1,,}"
+ if [ ! -z "$opt" ] && [ "$opt" = "save" ]; then
+ echo "sessionid: $hilink_sessID" > $hilink_save_file
+ echo "token: $hilink_token" >> $hilink_save_file
+ echo "tokenlist: ${hilink_tokenlist[@]}" >> $hilink_save_file
+ fi
+ _logout
+ hilink_tokenlist=""
+ hilink_sessID=""
+ hilink_token=""
+ return 0
+}
+
+# get status (connection status, DNS, )
+# parameter: none
+function _getStatus() {
+ if _login; then
+ if _sendRequest "api/monitoring/status"; then
+ if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
+ fi
+ return $?
+ fi
+ return 1
+}
+
+function _isConnected() {
+ local conn
+ conn=$(_getStatus "connectionstatus")
+ status="NO"
+ if [ ! -z "$conn" ] && [ $conn -eq 901 ]; then
+ status="YES"
+ return 0
+ fi
+ return 1
+}
+
+# get device information (device name, imei, imsi, msisdn-phone number, MAC, WAN IP ...)
+# parameter: name of parameter to return
+function _getDeviceInformation() {
+ if _login; then
+ if _sendRequest "api/device/information"; then
+ if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
+ fi
+ return $?
+ fi
+ return 1
+}
+
+# get net provider information
+# parameter: name of parameter to return
+function _getNetProvider() {
+ if _login; then
+ if _sendRequest "api/net/current-plmn"; then
+ if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
+ fi
+ return $?
+ fi
+ return 1
+}
+
+# get signal level
+# parameter: name of parameter to return
+function _getSignal() {
+ if _login; then
+ if _sendRequest "api/device/signal"; then
+ if [ ! -z "$1" ]; then _valueFromResponse "$1"; fi
+ fi
+ return $?
+ fi
+ return 1
+}
+
+function _getAllInformations() {
+ if _getDeviceInformation; then _keyValuePairs; fi
+ if _getSignal; then _keyValuePairs; fi
+ if _getNetProvider; then _keyValuePairs; fi
+}
+
+# get status of mobile data connection
+# parameter: none
+function _getMobileDataStatus() {
+ if _login; then
+ if _sendRequest "api/dialup/mobile-dataswitch"; then
+ status=$(_valueFromResponse "dataswitch")
+ if [ $? -eq 0 ] && [ ! -z "$status" ]; then echo "$status"; fi
+ fi
+ return $?
+ fi
+ return 1
+}
+
+
+# PIN of SIM can be passed either as $hilink_pin, or as parameter
+# parameter: PIN number of SIM card
+function _enableSIM() {
+#SimState:
+#255 - no SIM,
+#256 - error CPIN,
+#257 - ready,
+#258 - PIN disabled,
+#259 - check PIN,
+#260 - PIN required,
+#261 - PUK required
+ local simstate
+ if [ ! -z "$1" ]; then hilink_pin="$1"; fi
+ if ! _login; then return 1; fi
+ if _sendRequest "api/pin/status"; then
+ simstate=$(echo $response | sed -rn 's/.*
([0-9]*)<\/simstate>.*/\1/pi')
+ if [[ $simstate -eq 257 ]]; then status="SIM ready"; return 0; fi
+ if [[ $simstate -eq 260 ]]; then
+ status="PIN required"
+ if [ ! -z "$hilink_pin" ]; then _setPIN "$hilink_pin"; fi
+ return $?
+ fi
+ if [[ $simstate -eq 255 ]]; then status="NO SIM"; return 1; fi
+ fi
+ return 1
+}
+
+# obtain session and verification token - stored in vars $hilink_sessID and $token
+# parameter: none
+function _sessToken() {
+ hilink_tokenlist=""
+ hilink_token=""
+ hilink_sessID=""
+ response=$(curl -s http://$hilink_host/api/webserver/SesTokInfo -m 5 2> /dev/null)
+ if [ -z "$response" ]; then echo "No access to device at $hilink_host"; return 1; fi
+ status=$(echo "$response" | sed -nr 's/.*([0-9]*)<\/code>.*/\1/ip')
+ if [ -z "$status" ]; then
+ hilink_token=$(echo $response | sed -r 's/.*(.*)<\/TokInfo>.*/\1/')
+ hilink_sessID=$(echo $response | sed -r 's/.*(.*)<\/SesInfo>.*/\1/')
+ if [ ! -z "$hilink_sessID" ] && [ ! -z "$hilink_token" ]; then
+ hilink_sessID="SessionID=$hilink_sessID"
+ return 0
+ fi
+ fi
+ return 1
+}
+
+# unlock device (if locked) with user name and password
+# requires stored hilink_user="admin"; hilink_password"1234secret";hilink_host=$hilink_host_default
+# parameter: none
+function _login() {
+ local ret encpw pwtype pwtype3 hashedpw pwtype4
+ if _loginState; then return 0; fi # login not required, or already done
+ _sessToken
+ # get password type
+ if ! _sendRequest "api/user/state-login"; then return 1; fi
+ pwtype=$(echo "$response" | sed -rn 's/.*([0-9])<\/password_type>.*/\1/pi')
+ if [ -z "$pwtype" ];then pwtype=4; fi # fallback is type 4
+ ret=1
+ if [[ ! -z "$hilink_user" ]] && [[ ! -z "$hilink_password" ]]; then
+ # password encoding
+ # type 3 : base64(pw) encoded
+ # type 4 : base64(sha256sum(user + base64(sha256sum(pw)) + token))
+ pwtype3=$(echo -n "$hilink_password" | base64 --wrap=0)
+ hashedpw=$(echo -n "$hilink_password" | sha256sum -b | sed -nr 's/^([0-9a-z]*).*$/\1/ip' )
+ hashedpw=$(echo -n "$hashedpw" | base64 --wrap=0)
+ pwtype4=$(echo -n "$hilink_user$hashedpw$hilink_token" | sha256sum -b | sed -nr 's/^([0-9a-z]*).*$/\1/ip' )
+ encpw=$(echo -n "$pwtype4" | base64 --wrap=0)
+ if [ $pwtype -ne 4 ]; then encpw=$pwtype3; fi
+ hilink_xmldata="$hilink_user$encpw$pwtype"
+ hilink_xtraopts="--dump-header $hilink_header_file"
+ rm -f $hilink_header_file
+ _sendRequest "api/user/login"
+ if [ ! -z "$status" ] && [ "$status" = "OK" ]; then
+ # store the list of 30 tokens. Each token is valid for a single request
+ hilink_tokenlist=( $(cat $hilink_header_file | sed -rn 's/^__RequestVerificationToken:\s*([0-9a-z#]*).*$/\1/pi' | sed 's/#/ /g') )
+ _getToken
+ hilink_sessID=$(cat $hilink_header_file | grep -ioP 'SessionID=([a-z0-9]*)')
+ if [ ! -z "$hilink_sessID" ] && [ ! -z "$hilink_token" ]; then ret=0; fi
+ fi
+ rm -f $hilink_header_file
+ fi
+ return $ret
+}
+
+# logout of hilink device
+# parameter: none
+function _logout() {
+ if _loginState; then
+ hilink_xmldata="1"
+ if _sendRequest "api/user/logout"; then
+ hilink_tokenlist=""
+ hilink_sessID=""
+ hilink_token=""
+ hilink_login_enabled=""
+ fi
+ return $?
+ fi
+ return 1
+}
+
+# parameter: none
+function _loginState() {
+ local state
+ status="OK"
+ if [ -z "$hilink_login_enabled" ]; then _checkLoginEnabled; fi
+ if [ $hilink_login_enabled -eq 1 ]; then return 0; fi # login is disabled
+ _sendRequest "api/user/state-login"
+ state=`echo "$response" | sed -rn 's/.*(.*)<\/state>.*/\1/pi'`
+ if [ ! -z "$state" ] && [ $state -eq 0 ]; then # already logged in
+ return 0
+ fi
+ return 1
+}
+
+function _checkLoginEnabled() {
+ local state
+ if _sendRequest "api/user/hilink_login"; then
+ hilink_login_enabled=0
+ state=$(echo $response | sed -rn 's/.*(.*)<\/hilink_login>.*/\1/pi')
+ if [ ! -z "$state" ] && [ $state -eq 0 ]; then # no login enabled
+ hilink_login_enabled=1
+ fi
+ else
+ hilink_login_enabled=""
+ fi
+}
+
+# switch mobile data on/off 1/0
+# if SIM is locked, $hilink_pin has to be set
+# parameter: state - ON/OFF or 1/0
+function _switchMobileData() {
+ local mode
+ if [ -z "$1" ]; then return 1; fi
+ _login
+ mode="${1,,}"
+ [ "$mode" = "on" ] && mode=1
+ [ "$mode" = "off" ] && mode=0
+ if [[ $mode -ge 0 ]]; then
+ if _enableSIM "$hilink_pin"; then
+ hilink_xmldata="$mode"
+ _sendRequest "api/dialup/mobile-dataswitch"
+ return $?
+ fi
+ fi
+ return 1
+}
+
+# parameter: PIN of SIM card
+function _setPIN() {
+ local pin
+ if [ -z "$1" ]; then return 1; fi
+ pin="$1"
+ hilink_xmldata="0$pin"
+ _sendRequest "api/pin/operate"
+ return $?
+}
+
+# Send request to host at http://$hilink_host/$apiurl
+# data in $hilink_xmldata and options in $hilink_xtraopts
+# parameter: apiurl (e.g. "api/user/login")
+function _sendRequest() {
+ local ret apiurl
+ status="ERROR"
+ if [ -z "$1" ]; then return 1; fi
+ apiurl="$1"
+ ret=1
+ if [ -z "$hilink_sessID" ] || [ -z "$hilink_token" ]; then _sessToken; fi
+ if [ -z "$hilink_xmldata" ];then
+ response=$(curl -s http://$hilink_host/$apiurl -m 10 \
+ -H "Cookie: $hilink_sessID")
+ else
+ response=$(curl -s -X POST http://$hilink_host/$apiurl -m 10 \
+ -H "Content-Type: text/xml" \
+ -H "Cookie: $hilink_sessID" \
+ -H "__RequestVerificationToken: $hilink_token" \
+ -d "$hilink_xmldata" $hilink_xtraopts 2> /dev/null)
+ _getToken
+ fi
+ if [ ! -z "$response" ];then
+ response=$(echo $response | tr -d '\012\015') # delete newline chars
+ status=$(echo "$response" | sed -nr 's/.*([0-9]*)<\/code>.*/\1/ip') # check for error code
+ if [ -z "$status" ]; then
+ status="OK"
+ response=$(echo "$response" | sed -nr 's/.*(.*)<\/response>.*/\1/ip')
+ [ -z "$response" ] && response="none"
+ ret=0
+ else
+ status="ERROR $status"
+ fi
+ else
+ status="ERROR"
+ fi
+ if [[ "$status" =~ ERROR ]]; then _handleError; fi
+ hilink_xtraopts=""
+ hilink_xmldata=""
+ return $ret
+}
+
+# handle the list of tokens available after login
+# parameter: none
+function _getToken() {
+ if [ ! -z "$hilink_tokenlist" ] && [ ${#hilink_tokenlist[@]} -gt 0 ]; then
+ hilink_token=${hilink_tokenlist[0]} # get first token in list
+ hilink_tokenlist=("${hilink_tokenlist[@]:1}") # remove used token from list
+ if [ ${#hilink_tokenlist[@]} -eq 0 ]; then
+ _logout # use the last token to logout
+ fi
+ else
+ _sessToken # old token has been used - need new session
+ fi
+}
+
+# Analyse $status for error code
+# return error text in $status
+function _handleError() {
+ local ret txt
+ txt=$(_getErrorText)
+ if [ -z "$code" ]; then return 1; fi
+ ret=0
+ case "$code" in
+ 101|108003|108007)
+ ret=1
+ status="$txt"
+ ;;
+ 108001|108002|108006)
+ ret=1
+ status="$txt"
+ ;;
+ 125001|125002|125003)
+ _sessToken
+ ret=0
+ ;;
+ *)
+ ;;
+ esac
+ return "$ret"
+}
+
+declare -A hilink_err_api
+hilink_err_api[101]="Unable to get session ID/token"
+hilink_err_api[108001]="Invalid username/password"
+hilink_err_api[108002]=${hilink_err_api[108001]}
+hilink_err_api[108006]=${hilink_err_api[108001]}
+hilink_err_api[108003]="User already logged in - need to wait a bit"
+hilink_err_api[108007]="Too many login attempts - need to wait a bit"
+hilink_err_api[125001]="Invalid session/request token"
+hilink_err_api[125002]=${hilink_err_api[125001]}
+hilink_err_api[125003]=${hilink_err_api[125001]}
+
+# check error and return error text
+# status passsed in $status, or $1
+function _getErrorText() {
+ local err code errortext
+ err="$status"
+ code="0"
+ if [ ! -z "$1" ]; then err="$1"; fi
+ if [ -z "$err" ]; then return 1; fi
+ errortext="$err"
+ if [[ "$err" =~ ERROR\ *([0-9]*) ]] && [ ! -z "${BASH_REMATCH[1]}" ]; then
+ code=${BASH_REMATCH[1]}
+ if [ ! -z "$code" ] && [ ! -z "${hilink_err_api[$code]}" ]; then
+ errortext="${hilink_err_api[$code]}"
+ fi
+ fi
+ echo $errortext
+ return 0
+}
+
+function _hostReachable() {
+ local avail
+ avail=$( timeout 0.5 ping -c 1 $hilink_host | sed -rn 's/.*time=.*/1/p' )
+ if [ -z "$avail" ]; then status="ERROR: Not reachable"; return 1; fi
+ status="OK"
+ return 0;
+}
+
+# helper function to parse $response (xml format) for a value
+# call another function first!
+# parameter: tag-name
+function _valueFromResponse() {
+ local par value
+ if [ -z "$response" ] || [ -z "$1" ]; then return 1; fi
+ par="$1"
+ value=$(echo $response | sed -rn 's/.*<'$par'>(.*)<\/'$par'>.*/\1/pi')
+ if [ -z "$value" ]; then return 1; fi
+ echo "$value"
+ return 0
+}
+
+# list all keys of the current xml response
+function _keysFromResponse() {
+ if [ -z "$response" ]; then return 1; fi
+ echo $response | grep -oiP "(?<=<)[a-z_-]*(?=>)"
+ return 0
+}
+
+# return all key=value pairs of the current xml response
+function _keyValuePairs() {
+ if [ -z "$response" ]; then return 1; fi
+ echo $response | sed -n 's/<\([^>]*\)>\(.*\)<\/\1>[^<]*/\1=\"\2\"\n/gpi'
+ return 0
+}
+
+hilink_token=""
+hilink_tokenlist=""
+hilink_sessID=""
+hilink_xmldata=""
+hilink_xtraopts=""
+hilink_host=$hilink_host_default
+hilink_user="admin"
+hilink_password=""
+hilink_pin=""
+response=""
+status=""
+
\ No newline at end of file
diff --git a/config/client_config/info_huawei.sh b/config/client_config/info_huawei.sh
new file mode 100644
index 00000000..fe6c27a1
--- /dev/null
+++ b/config/client_config/info_huawei.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+# get info about device and signal of Huawei mobile USB devices
+# parm:
+# $1 : requested information (manufacturer, device, imei, imsi, telnumber, ipaddress, mode, signal, operator)
+# $2 : (optional) type - hilink or modem (default: hilink)
+# $3 : (optional) for hilink: ip address of the device (default: 192.168.8.1)
+# for modem: tty interface for communication (default: /dev/ttypUSB2)
+# $4 : more options can be added for Hilink devices ('-u user -P password -p pin'). These are passed to the corresponding script
+#
+# requires: bc
+# calls the scripts info_huawei_hilink.sh and info_huawei_modem.sh (same path as this script)
+#
+# zbchristian 2020
+#
+path=$(dirname "$0")
+opt="device"
+if [ ! -z "$1" ]; then opt=${1,,}; fi
+type="hilink"
+if [ ! -z "$2" ]; then type=${2,,}; fi
+
+parms=""
+if [ "$type" = "hilink" ]; then
+ connect="-h 192.168.8.1"
+ if [ ! -z "$3" ]; then connect="-h $3"; fi
+ if [ ! -z "$4" ]; then parms="$4"; fi
+ script="$path/info_huawei_hilink.sh"
+else
+ connect="/dev/ttyUSB2"
+ if [ ! -z "$3" ]; then connect=$3; fi
+ script="$path/info_huawei_modem.sh"
+fi
+res=$($script $opt $connect $parms)
+
+# some results require special treatment
+case $opt in
+
+# manufacturer)
+# if [ "$res" = "none" ]; then res="Huawei"; fi
+# ;;
+
+# device)
+# if [ ! "$res" = "none" ]; then res="Huawei $res";
+# else res="Huawei"; fi
+# ;;
+
+ mode)
+ if [ ! "$res" = "none" ]; then
+ if [ "$type" = "hilink" ]; then
+ if [ "$res" = "LTE" ]; then res="4G"
+ elif [ "$res" = "WCDMA" ]; then res="3G";
+ else res="2G"; fi
+ else
+ if [ $res -eq 7 ]; then res="4G"
+ elif [ $res -lt 7 ] && [ $res -gt 2 ] ; then res="3G";
+ else res="2G"; fi
+ fi
+ fi
+ ;;
+
+ signal)
+ # return signal strength/quality in %
+ if [ "$type" = "hilink" ]; then
+ # signal request tries to get RSRQ value
+ # try to get RSRQ (4G), EC/IO (3G) or RSSI (2G) value
+ if [ "$res" = "none" ]; then res=$($script "ecio"); fi
+ if [ ! "$res" = "none" ]; then
+ # for rsrq and ecio assume: -3dB (100%) downto -20dB (0%)
+ qual=${res//dB/}
+ if [[ ! "$qual" =~ [-0-9\.]* ]]; then qual=-100; fi
+ qual=$(bc <<< "scale=0;res=$qual-0.5;res/1") # just round to next integer
+ if [ $qual -le -20 ]; then qual=0;
+ elif [ $qual -ge -3 ]; then qual=100;
+ else qual=$(bc <<< "scale=0;res=100.0/17.0*$qual+2000.0/17.0;res/1"); fi
+ else
+ # try rssi: >-70dBm (100%) downto -100dBm (0%)
+ res=$($script "rssi");
+ if [ ! "$res" = "none" ]; then
+ if [[ ! $res =~ [-0-9\.]* ]]; then res="-120 dBm"; fi
+ qual=${res//dBm/}
+ qual=$(bc <<< "scale=0;res=$qual+0.5;res/1") # just round to next integer
+ if [ $qual -le -110 ]; then qual=0;
+ elif [ $qual -ge -70 ]; then qual=100;
+ else qual=$(bc <<< "scale=0;res=2.5*$qual+275;res/1"); fi
+ fi
+ fi
+ else
+ # modem returns RSSI as number 0-31 - 0 = -113dB (0%), 1 = -111dB, 31 = >=51dB (100%)
+ qual=$(bc <<< "scale=0;res=$res*3.5+0.5;res/1")
+ if [ $qual -gt 100 ]; then res=100; fi
+ fi
+ if [ ! "$res" = "none" ]; then res="$res (${qual}%)"; fi
+ ;;
+
+ operator)
+ # check if operator/network is just a 5 digit number -> extract network name from table
+ if [[ $res =~ ^[0-9]{5}$ ]]; then
+ mcc=${res:0:3}
+ mnc=${res:3:2}
+ op=$(cat $path/mcc-mnc-table.csv | sed -rn 's/^'$mcc'\,[0-9]*\,'$mnc'\,(.*\,){4}(.*)$/\2/p')
+ if [ ! -z "$op" ]; then res="$op ($res)"; fi
+ fi
+ ;;
+
+ *)
+ ;;
+esac
+
+echo $res
+
diff --git a/config/client_config/info_huawei_hilink.sh b/config/client_config/info_huawei_hilink.sh
new file mode 100644
index 00000000..c98ee9fe
--- /dev/null
+++ b/config/client_config/info_huawei_hilink.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# Information about HUAWEI hilink
+# -------------------------------
+# get info about the device and signal
+# parameter: $1 - "connected", "device", "ipaddress", "mode", "signal" (see case statement below)
+# -u,--user - username
+# -P,--password - password
+# -p,--pin - SIM pin
+# -h,--host - host ip address for API calls (optional)
+# returns the value of the parameter, or "none" if not found or empty
+#
+# All device informations are buffered for 5 secs to speed up subsequent calls
+#
+# zbchristian 2021
+
+function _setAPIParams() {
+ if [ ! -z "$hostip" ]; then hilink_host="$hostip"; fi
+ if [ ! -z "$username" ]; then hilink_user="$username"; fi
+ if [ ! -z "$password" ]; then hilink_password="$password"; fi
+ if [ ! -z "$simpin" ]; then hilink_pin="$simpin"; fi
+}
+
+if [ -z "$1" ]; then echo "none"; exit; fi
+property="${1,,}"
+shift
+hostip="192.168.8.1"
+while [ -n "$1" ]; do
+ case "$1" in
+ -u|--user) username="$2"; shift ;;
+ -P|--password) password="$2"; shift ;;
+ -p|--pin) simpin="$2"; shift ;;
+ -h|--host) hostip="$2"; shift ;;
+ esac
+ shift
+done
+
+status="no valid option given"
+result="none"
+hostip="192.168.8.1"
+if [ "$opt" = "connected" ]; then
+ source /usr/local/sbin/huawei_hilink_api.sh
+ _setAPIParams
+ if ! _initHilinkAPI; then echo "none"; exit; fi
+ result=$(_getMobileDataStatus)
+ _closeHilinkAPI
+else
+ info_file="/tmp/huawei_infos_${hostip}_$(id -u).dat"
+ if [ -f "$info_file" ]; then
+ age=$(( $(date +%s) - $(stat $info_file -c %Y) ))
+ if [[ $age -gt 10 ]]; then rm -f $info_file; fi
+ fi
+
+ if [ -f "$info_file" ]; then
+ infos=$(cat $info_file)
+ else
+ source /usr/local/sbin/huawei_hilink_api.sh
+ _setAPIParams
+ if ! _initHilinkAPI; then echo "none"; exit; fi
+ infos=$(_getAllInformations)
+ _closeHilinkAPI
+ if [ ! -z "$infos" ]; then echo -n "$infos" > $info_file; fi
+ fi
+
+ case "$property" in
+ device|devicename)
+ key="devicename"
+ ;;
+ ipaddress|wanipaddress)
+ key="wanipaddress"
+ ;;
+ mode)
+ key="workmode"
+ ;;
+ telnumber)
+ key="msisdn"
+ ;;
+ imei|imsi|rssi|rsrq|rsrp|sinr|ecio)
+ key="$property"
+ ;;
+ signal)
+ key="rsrq"
+ ;;
+ operator|fullname)
+ key="fullname"
+ ;;
+ *)
+ key="device"
+ ;;
+ esac
+ if [ -z "$key" ]; then result="none"; fi
+ result=$(echo "$infos" | sed -rn 's/'$key'=\"([^ \s]*)\"/\1/ip')
+ if [ -z "$result" ]; then result="none"; fi
+fi
+echo -n "$result"
+
diff --git a/config/client_config/info_huawei_modem.sh b/config/client_config/info_huawei_modem.sh
new file mode 100644
index 00000000..33c78b7d
--- /dev/null
+++ b/config/client_config/info_huawei_modem.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# Information about HUAWEI modem - via AT commands
+# ------------------------------------------------
+# get info about the device and signal
+# parameter: $1 - see opts list below
+# $2 - tty device name for the communicaton (optional)
+# returns the value of the parameter, or "none" if not found or empty
+#
+# requires: socat
+#
+# zbchristian 2020
+
+opts=("manufacturer" "device" "imei" "imsi" "telnumber" "mode" "signal" "operator")
+
+# at command to extract information
+atcmds=("AT+CGMI" "AT+CGMM" "AT+CGSN" "AT+CIMI" "AT+CNUM" "AT+COPS?" "AT+CSQ" "AT+COPS?")
+# regexp pattern to extract wanted information from result string
+pats=( " " " " " " " " ".*\,\"([0-9\+]*)\".*" '.*\,([0-9])$' ".*: ([0-9]*).*" '.*\,\"([^ ]*)\".*$')
+
+# tty device for communication - usually 3 tty devices are created and the 3rd ttyUSB2 is available, even, when the device is connected
+dev="/dev/ttyUSB2"
+
+atsilent="AT^CURC=0"
+
+if [ ! -z $2 ]; then dev=$2; fi
+
+idx=-1
+opt=${opts[0]}
+if [ ! -z $1 ]; then opt=$1; fi
+
+for i in "${!opts[@]}"; do
+ if [[ ${opts[$i]} == $opt ]]; then idx=$i; fi
+done
+if [[ $idx == -1 ]];then echo "none"; exit; fi
+
+atcmd=${atcmds[$idx]}
+pat=${pats[$idx]}
+
+
+result=`(echo $atsilent; echo $atcmd) | sudo /usr/bin/socat - $dev`
+# escape the AT command to be used in the regexp
+atesc=${atcmd//[\+]/\\+}
+atesc=${atesc//[\?]/\\?}
+result=`echo $result | sed -rn 's/.*'"$atesc"'\s([^ ]+|[^ ]+ [^ ]+)\sOK.*$/\1/pg'`
+if [[ $pat != " " ]]; then
+ result=`echo $result | sed -rn 's/'"$pat"'/\1/pg'`
+fi
+
+if [ -z "$result" ]; then result="none"; fi
+
+echo $result
+
diff --git a/config/client_config/interfaces b/config/client_config/interfaces
new file mode 100644
index 00000000..157aa22b
--- /dev/null
+++ b/config/client_config/interfaces
@@ -0,0 +1,13 @@
+# interfaces(5) file used by ifup(8) and ifdown(8)
+
+# Please note that this file is written to be used with dhcpcd
+# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'
+
+# Include files from /etc/network/interfaces.d:
+source-directory /etc/network/interfaces.d
+
+auto ppp0
+iface ppp0 inet wvdial
+provider connect
+pre-up /usr/local/sbin/ppp0_setpin.sh
+up /usr/local/sbin/ppp0_route.sh
diff --git a/config/client_config/mcc-mnc-table.csv b/config/client_config/mcc-mnc-table.csv
new file mode 100644
index 00000000..34510563
--- /dev/null
+++ b/config/client_config/mcc-mnc-table.csv
@@ -0,0 +1,1691 @@
+MCC,MCC (int),MNC,MNC (int),ISO,Country,Country Code,Network
+289,649,88,2191,ge,Abkhazia,7,A-Mobile
+289,649,68,1679,ge,Abkhazia,7,A-Mobile
+289,649,67,1663,ge,Abkhazia,7,Aquafon
+412,1042,80,2063,af,Afghanistan,93,Afghan Telecom Corp. (AT)
+412,1042,88,2191,af,Afghanistan,93,Afghan Telecom Corp. (AT)
+412,1042,01,31,af,Afghanistan,93,Afghan Wireless/AWCC
+412,1042,40,1039,af,Afghanistan,93,Areeba/MTN
+412,1042,50,1295,af,Afghanistan,93,Etisalat
+412,1042,30,783,af,Afghanistan,93,Etisalat
+412,1042,20,527,af,Afghanistan,93,Roshan/TDCA
+412,1042,03,63,af,Afghanistan,93,WaselTelecom (WT)
+276,630,01,31,al,Albania,355,AMC/Cosmote
+276,630,03,63,al,Albania,355,Eagle Mobile
+276,630,04,79,al,Albania,355,PLUS Communication Sh.a
+276,630,02,47,al,Albania,355,Vodafone
+603,1539,01,31,dz,Algeria,213,ATM Mobils
+603,1539,02,47,dz,Algeria,213,Orascom / DJEZZY
+603,1539,03,63,dz,Algeria,213,Oreedo/Wataniya / Nedjma
+544,1348,11,287,as,American Samoa,684,Blue Sky Communications
+213,531,03,63,ad,Andorra,376,Mobiland
+631,1585,04,79,ao,Angola,244,MoviCel
+631,1585,02,47,ao,Angola,244,Unitel
+365,869,840,2112,ai,Anguilla,1264,Cable and Wireless
+365,869,010,16,ai,Anguilla,1264,Digicell / Wireless Vent. Ltd
+344,836,030,48,ag,Antigua and Barbuda,1268,APUA PCS
+344,836,920,2336,ag,Antigua and Barbuda,1268,C & W
+344,836,930,2352,ag,Antigua and Barbuda,1268,DigiCel/Cing. Wireless
+722,1826,310,784,ar,Argentina Republic,54,Claro/ CTI/AMX
+722,1826,330,816,ar,Argentina Republic,54,Claro/ CTI/AMX
+722,1826,320,800,ar,Argentina Republic,54,Claro/ CTI/AMX
+722,1826,010,16,ar,Argentina Republic,54,Compania De Radiocomunicaciones Moviles SA
+722,1826,070,112,ar,Argentina Republic,54,Movistar/Telefonica
+722,1826,020,32,ar,Argentina Republic,54,Nextel
+722,1826,340,832,ar,Argentina Republic,54,Telecom Personal S.A.
+722,1826,341,833,ar,Argentina Republic,54,Telecom Personal S.A.
+283,643,01,31,am,Armenia,374,ArmenTel/Beeline
+283,643,04,79,am,Armenia,374,Karabakh Telecom
+283,643,10,271,am,Armenia,374,Orange
+283,643,05,95,am,Armenia,374,Vivacell
+363,867,02,47,aw,Aruba,297,Digicel
+363,867,20,527,aw,Aruba,297,Digicel
+363,867,01,31,aw,Aruba,297,Setar GSM
+505,1285,14,335,au,Australia,61,AAPT Ltd.
+505,1285,24,591,au,Australia,61,Advanced Comm Tech Pty.
+505,1285,09,159,au,Australia,61,Airnet Commercial Australia Ltd..
+505,1285,04,79,au,Australia,61,Department of Defense
+505,1285,26,623,au,Australia,61,Dialogue Communications Pty Ltd
+505,1285,12,303,au,Australia,61,H3G Ltd.
+505,1285,06,111,au,Australia,61,H3G Ltd.
+505,1285,88,2191,au,Australia,61,Localstar Holding Pty. Ltd
+505,1285,19,415,au,Australia,61,Lycamobile Pty Ltd
+505,1285,08,143,au,Australia,61,Railcorp/Vodafone
+505,1285,99,2463,au,Australia,61,Railcorp/Vodafone
+505,1285,13,319,au,Australia,61,Railcorp/Vodafone
+505,1285,02,47,au,Australia,61,Singtel Optus
+505,1285,90,2319,au,Australia,61,Singtel Optus
+505,1285,01,31,au,Australia,61,Telstra Corp. Ltd.
+505,1285,11,287,au,Australia,61,Telstra Corp. Ltd.
+505,1285,71,1823,au,Australia,61,Telstra Corp. Ltd.
+505,1285,72,1839,au,Australia,61,Telstra Corp. Ltd.
+505,1285,05,95,au,Australia,61,The Ozitel Network Pty.
+505,1285,16,367,au,Australia,61,Victorian Rail Track Corp. (VicTrack)
+505,1285,07,127,au,Australia,61,Vodafone
+505,1285,03,63,au,Australia,61,Vodafone
+232,562,09,159,at,Austria,43,A1 MobilKom
+232,562,02,47,at,Austria,43,A1 MobilKom
+232,562,01,31,at,Austria,43,A1 MobilKom
+232,562,11,287,at,Austria,43,A1 MobilKom
+232,562,15,351,at,Austria,43,T-Mobile/Telering
+232,562,10,271,at,Austria,43,H3G
+232,562,14,335,at,Austria,43,H3G
+232,562,05,95,at,Austria,43,3/Orange/One Connect
+232,562,12,303,at,Austria,43,3/Orange/One Connect
+232,562,06,111,at,Austria,43,3/Orange/One Connect
+232,562,17,383,at,Austria,43,Spusu/Mass Response
+232,562,04,79,at,Austria,43,T-Mobile/Telering
+232,562,03,63,at,Austria,43,T-Mobile/Telering
+232,562,07,127,at,Austria,43,T-Mobile/Telering
+232,562,19,415,at,Austria,43,Tele2
+232,562,08,143,at,Austria,43,A1 MobilKom
+232,562,13,319,at,Austria,43,UPC Austria
+400,1024,01,31,az,Azerbaijan,994,Azercell Telekom B.M.
+400,1024,04,79,az,Azerbaijan,994,Azerfon.
+400,1024,03,63,az,Azerbaijan,994,Caspian American Telecommunications LLC (CATEL)
+400,1024,02,47,az,Azerbaijan,994,J.V. Bakcell GSM 2000
+364,868,390,912,bs,Bahamas,1242,Bahamas Telco. Comp.
+364,868,39,927,bs,Bahamas,1242,Bahamas Telco. Comp.
+364,868,30,783,bs,Bahamas,1242,Bahamas Telco. Comp.
+364,868,03,63,bs,Bahamas,1242,Smart Communications
+426,1062,01,31,bh,Bahrain,973,Batelco
+426,1062,02,47,bh,Bahrain,973,ZAIN/Vodafone
+426,1062,04,79,bh,Bahrain,973,VIVA
+470,1136,02,47,bd,Bangladesh,880,Robi/Aktel
+470,1136,05,95,bd,Bangladesh,880,Citycell
+470,1136,06,111,bd,Bangladesh,880,Citycell
+470,1136,01,31,bd,Bangladesh,880,GrameenPhone
+470,1136,03,63,bd,Bangladesh,880,Orascom/Banglalink
+470,1136,04,79,bd,Bangladesh,880,TeleTalk
+470,1136,07,127,bd,Bangladesh,880,Airtel/Warid
+342,834,600,1536,bb,Barbados,1246,LIME
+342,834,810,2064,bb,Barbados,1246,Cingular Wireless
+342,834,750,1872,bb,Barbados,1246,Digicel
+342,834,050,80,bb,Barbados,1246,Digicel
+342,834,820,2080,bb,Barbados,1246,Sunbeach
+257,599,03,63,by,Belarus,375,BelCel JV
+257,599,04,79,by,Belarus,375,BeST
+257,599,01,31,by,Belarus,375,Mobile Digital Communications
+257,599,02,47,by,Belarus,375,MTS
+206,518,20,527,be,Belgium,32,Base/KPN
+206,518,01,31,be,Belgium,32,Belgacom/Proximus
+206,518,06,111,be,Belgium,32,Lycamobile Belgium
+206,518,10,271,be,Belgium,32,Mobistar/Orange
+206,518,02,47,be,Belgium,32,SNCT/NMBS
+206,518,05,95,be,Belgium,32,Telenet BidCo NV
+702,1794,67,1663,bz,Belize,501,DigiCell
+702,1794,68,1679,bz,Belize,501,International Telco (INTELCO)
+616,1558,04,79,bj,Benin,229,Bell Benin/BBCOM
+616,1558,02,47,bj,Benin,229,Etisalat/MOOV
+616,1558,05,95,bj,Benin,229,GloMobile
+616,1558,01,31,bj,Benin,229,Libercom
+616,1558,03,63,bj,Benin,229,MTN/Spacetel
+350,848,000,0,bm,Bermuda,1441,Bermuda Digital Communications Ltd (BDC)
+350,848,99,2463,bm,Bermuda,1441,CellOne Ltd
+350,848,10,271,bm,Bermuda,1441,DigiCel / Cingular
+350,848,02,47,bm,Bermuda,1441,M3 Wireless Ltd
+350,848,01,31,bm,Bermuda,1441,Telecommunications (Bermuda & West Indies) Ltd (Digicel Bermuda)
+402,1026,11,287,bt,Bhutan,975,B-Mobile
+402,1026,17,383,bt,Bhutan,975,Bhutan Telecom Ltd (BTL)
+402,1026,77,1919,bt,Bhutan,975,TashiCell
+736,1846,02,47,bo,Bolivia,591,Entel Pcs
+736,1846,01,31,bo,Bolivia,591,Viva/Nuevatel
+736,1846,03,63,bo,Bolivia,591,Tigo
+218,536,90,2319,ba,Bosnia & Herzegov.,387,BH Mobile
+218,536,03,63,ba,Bosnia & Herzegov.,387,Eronet Mobile
+218,536,05,95,ba,Bosnia & Herzegov.,387,M-Tel
+652,1618,04,79,bw,Botswana,267,BeMOBILE
+652,1618,01,31,bw,Botswana,267,Mascom Wireless (Pty) Ltd.
+652,1618,02,47,bw,Botswana,267,Orange
+724,1828,12,303,br,Brazil,55,Claro/Albra/America Movil
+724,1828,38,911,br,Brazil,55,Claro/Albra/America Movil
+724,1828,05,95,br,Brazil,55,Claro/Albra/America Movil
+724,1828,01,31,br,Brazil,55,Vivo S.A./Telemig
+724,1828,32,815,br,Brazil,55,CTBC Celular SA (CTBC)
+724,1828,34,847,br,Brazil,55,CTBC Celular SA (CTBC)
+724,1828,33,831,br,Brazil,55,CTBC Celular SA (CTBC)
+724,1828,08,143,br,Brazil,55,TIM
+724,1828,00,15,br,Brazil,55,Nextel (Telet)
+724,1828,39,927,br,Brazil,55,Nextel (Telet)
+724,1828,30,783,br,Brazil,55,Oi (TNL PCS / Oi)
+724,1828,16,367,br,Brazil,55,Brazil Telcom
+724,1828,31,799,br,Brazil,55,Oi (TNL PCS / Oi)
+724,1828,24,591,br,Brazil,55,Amazonia Celular S/A
+724,1828,54,1359,br,Brazil,55,PORTO SEGURO TELECOMUNICACOES
+724,1828,15,351,br,Brazil,55,Sercontel Cel
+724,1828,07,127,br,Brazil,55,CTBC/Triangulo
+724,1828,19,415,br,Brazil,55,Vivo S.A./Telemig
+724,1828,03,63,br,Brazil,55,TIM
+724,1828,02,47,br,Brazil,55,TIM
+724,1828,04,79,br,Brazil,55,TIM
+724,1828,37,895,br,Brazil,55,Unicel do Brasil Telecomunicacoes Ltda
+724,1828,23,575,br,Brazil,55,Vivo S.A./Telemig
+724,1828,11,287,br,Brazil,55,Vivo S.A./Telemig
+724,1828,10,271,br,Brazil,55,Vivo S.A./Telemig
+724,1828,06,111,br,Brazil,55,Vivo S.A./Telemig
+348,840,570,1392,vg,British Virgin Islands,284,Caribbean Cellular
+348,840,770,1904,vg,British Virgin Islands,284,Digicel
+348,840,170,368,vg,British Virgin Islands,284,LIME
+528,1320,02,47,bn,Brunei Darussalam,673,b-mobile
+528,1320,11,287,bn,Brunei Darussalam,673,Datastream (DTSCom)
+528,1320,01,31,bn,Brunei Darussalam,673,Telekom Brunei Bhd (TelBru)
+284,644,06,111,bg,Bulgaria,359,BTC Mobile EOOD (vivatel)
+284,644,03,63,bg,Bulgaria,359,BTC Mobile EOOD (vivatel)
+284,644,05,95,bg,Bulgaria,359,Telenor/Cosmo/Globul
+284,644,01,31,bg,Bulgaria,359,MobilTel AD
+613,1555,03,63,bf,Burkina Faso,226,TeleCel
+613,1555,01,31,bf,Burkina Faso,226,TeleMob-OnaTel
+613,1555,02,47,bf,Burkina Faso,226,Airtel/ZAIN/CelTel
+642,1602,02,47,bi,Burundi,257,Africel / Safaris
+642,1602,08,143,bi,Burundi,257,Lumitel/Viettel
+642,1602,03,63,bi,Burundi,257,Onatel / Telecel
+642,1602,07,127,bi,Burundi,257,Smart Mobile / LACELL
+642,1602,01,31,bi,Burundi,257,Spacetel / Econet / Leo
+642,1602,82,2095,bi,Burundi,257,Spacetel / Econet / Leo
+456,1110,04,79,kh,Cambodia,855,Cambodia Advance Communications Co. Ltd (CADCOMMS)
+456,1110,02,47,kh,Cambodia,855,Smart Mobile
+456,1110,08,143,kh,Cambodia,855,Metfone
+456,1110,18,399,kh,Cambodia,855,MFone/Camshin/Cellcard
+456,1110,01,31,kh,Cambodia,855,Mobitel/Cam GSM
+456,1110,03,63,kh,Cambodia,855,QB/Cambodia Adv. Comms.
+456,1110,05,95,kh,Cambodia,855,Smart Mobile
+456,1110,06,111,kh,Cambodia,855,Smart Mobile
+456,1110,09,159,kh,Cambodia,855,Sotelco/Beeline
+624,1572,01,31,cm,Cameroon,237,MTN
+624,1572,04,79,cm,Cameroon,237,Nextel
+624,1572,02,47,cm,Cameroon,237,Orange
+302,770,652,1618,ca,Canada,1,BC Tel Mobility
+302,770,630,1584,ca,Canada,1,Bell Aliant
+302,770,651,1617,ca,Canada,1,Bell Mobility
+302,770,610,1552,ca,Canada,1,Bell Mobility
+302,770,670,1648,ca,Canada,1,CityWest Mobility
+302,770,360,864,ca,Canada,1,Clearnet
+302,770,361,865,ca,Canada,1,Clearnet
+302,770,380,896,ca,Canada,1,DMTS Mobility
+302,770,710,1808,ca,Canada,1,Globalstar Canada
+302,770,640,1600,ca,Canada,1,Latitude Wireless
+302,770,370,880,ca,Canada,1,FIDO (Rogers AT&T/ Microcell)
+302,770,320,800,ca,Canada,1,mobilicity
+302,770,702,1794,ca,Canada,1,MT&T Mobility
+302,770,655,1621,ca,Canada,1,MTS Mobility
+302,770,660,1632,ca,Canada,1,MTS Mobility
+302,770,701,1793,ca,Canada,1,NB Tel Mobility
+302,770,703,1795,ca,Canada,1,New Tel Mobility
+302,770,760,1888,ca,Canada,1,Public Mobile
+302,770,657,1623,ca,Canada,1,Quebectel Mobility
+302,770,720,1824,ca,Canada,1,Rogers AT&T Wireless
+302,770,654,1620,ca,Canada,1,Sask Tel Mobility
+302,770,680,1664,ca,Canada,1,Sask Tel Mobility
+302,770,780,1920,ca,Canada,1,Sask Tel Mobility
+302,770,656,1622,ca,Canada,1,Tbay Mobility
+302,770,220,544,ca,Canada,1,Telus Mobility
+302,770,653,1619,ca,Canada,1,Telus Mobility
+302,770,500,1280,ca,Canada,1,Videotron
+302,770,490,1168,ca,Canada,1,WIND
+625,1573,01,31,cv,Cape Verde,238,CV Movel
+625,1573,02,47,cv,Cape Verde,238,T+ Telecom
+346,838,050,80,ky,Cayman Islands,1345,Digicel Cayman Ltd
+346,838,006,6,ky,Cayman Islands,1345,Digicel Ltd.
+346,838,140,320,ky,Cayman Islands,1345,LIME / Cable & Wirel.
+623,1571,01,31,cf,Central African Rep.,236,Centrafr. Telecom+
+623,1571,04,79,cf,Central African Rep.,236,Nationlink
+623,1571,03,63,cf,Central African Rep.,236,Orange/Celca
+623,1571,02,47,cf,Central African Rep.,236,Telecel Centraf.
+622,1570,04,79,td,Chad,235,Salam/Sotel
+622,1570,02,47,td,Chad,235,Tchad Mobile
+622,1570,03,63,td,Chad,235,Tigo/Milicom/Tchad Mobile
+622,1570,01,31,td,Chad,235,Airtel/ZAIN/Celtel
+730,1840,06,111,cl,Chile,56,Blue Two Chile SA
+730,1840,11,287,cl,Chile,56,Celupago SA
+730,1840,15,351,cl,Chile,56,Cibeles Telecom SA
+730,1840,03,63,cl,Chile,56,Claro
+730,1840,10,271,cl,Chile,56,Entel Telefonia
+730,1840,01,31,cl,Chile,56,Entel Telefonia Mov
+730,1840,14,335,cl,Chile,56,Netline Telefonica Movil Ltda
+730,1840,04,79,cl,Chile,56,Nextel SA
+730,1840,09,159,cl,Chile,56,Nextel SA
+730,1840,05,95,cl,Chile,56,Nextel SA
+730,1840,19,415,cl,Chile,56,Sociedad Falabella Movil SPA
+730,1840,02,47,cl,Chile,56,TELEFONICA
+730,1840,07,127,cl,Chile,56,TELEFONICA
+730,1840,12,303,cl,Chile,56,Telestar Movil SA
+730,1840,00,15,cl,Chile,56,TESAM SA
+730,1840,13,319,cl,Chile,56,Tribe Mobile SPA
+730,1840,08,143,cl,Chile,56,VTR Banda Ancha SA
+460,1120,07,127,cn,China,86,China Mobile GSM
+460,1120,02,47,cn,China,86,China Mobile GSM
+460,1120,00,15,cn,China,86,China Mobile GSM
+460,1120,04,79,cn,China,86,China Space Mobile Satellite Telecommunications Co. Ltd (China Spacecom)
+460,1120,05,95,cn,China,86,China Telecom
+460,1120,03,63,cn,China,86,China Telecom
+460,1120,06,111,cn,China,86,China Unicom
+460,1120,01,31,cn,China,86,China Unicom
+732,1842,130,304,co,Colombia,57,Avantel SAS
+732,1842,102,258,co,Colombia,57,Movistar
+732,1842,103,259,co,Colombia,57,TIGO/Colombia Movil
+732,1842,001,1,co,Colombia,57,TIGO/Colombia Movil
+732,1842,101,257,co,Colombia,57,Comcel S.A. Occel S.A./Celcaribe
+732,1842,002,2,co,Colombia,57,Edatel S.A.
+732,1842,187,391,co,Colombia,57,eTb
+732,1842,123,291,co,Colombia,57,Movistar
+732,1842,111,273,co,Colombia,57,TIGO/Colombia Movil
+732,1842,142,322,co,Colombia,57,UNE EPM Telecomunicaciones SA ESP
+732,1842,020,32,co,Colombia,57,UNE EPM Telecomunicaciones SA ESP
+732,1842,154,340,co,Colombia,57,Virgin Mobile Colombia SAS
+654,1620,01,31,km,Comoros,269,HURI - SNPT
+630,1584,90,2319,cd,Congo Dem. Rep.,243,Africell
+630,1584,86,2159,cd,Congo Dem. Rep.,243,Orange RDC sarl
+630,1584,05,95,cd,Congo Dem. Rep.,243,SuperCell
+630,1584,89,2207,cd,Congo Dem. Rep.,243,TIGO/Oasis
+630,1584,01,31,cd,Congo Dem. Rep.,243,Vodacom
+630,1584,88,2191,cd,Congo Dem. Rep.,243,Yozma Timeturns sprl (YTT)
+630,1584,02,47,cd,Congo Dem. Rep.,243,Airtel/ZAIN
+629,1577,01,31,cg,Congo Republic,242,Airtel SA
+629,1577,02,47,cg,Congo Republic,242,Azur SA (ETC)
+629,1577,10,271,cg,Congo Republic,242,MTN/Libertis
+629,1577,07,127,cg,Congo Republic,242,Warid
+548,1352,01,31,ck,Cook Islands,682,Telecom Cook Islands
+712,1810,03,63,cr,Costa Rica,506,Claro
+712,1810,02,47,cr,Costa Rica,506,ICE
+712,1810,01,31,cr,Costa Rica,506,ICE
+712,1810,04,79,cr,Costa Rica,506,Movistar
+712,1810,20,527,cr,Costa Rica,506,Virtualis
+219,537,01,31,hr,Croatia,385,T-Mobile/Cronet
+219,537,02,47,hr,Croatia,385,Tele2
+219,537,10,271,hr,Croatia,385,VIPnet d.o.o.
+368,872,01,31,cu,Cuba,53,C-COM
+362,866,95,2399,cw,Curacao,599,EOCG Wireless NV
+362,866,69,1695,cw,Curacao,599,Polycom N.V./ Digicel
+280,640,10,271,cy,Cyprus,357,MTN/Areeba
+280,640,20,527,cy,Cyprus,357,PrimeTel PLC
+280,640,01,31,cy,Cyprus,357,Vodafone/CyTa
+230,560,08,143,cz,Czech Rep.,420,Compatel s.r.o.
+230,560,02,47,cz,Czech Rep.,420,O2
+230,560,01,31,cz,Czech Rep.,420,T-Mobile / RadioMobil
+230,560,05,95,cz,Czech Rep.,420,Travel Telekommunikation s.r.o.
+230,560,04,79,cz,Czech Rep.,420,Ufone
+230,560,99,2463,cz,Czech Rep.,420,Vodafone
+230,560,03,63,cz,Czech Rep.,420,Vodafone
+238,568,05,95,dk,Denmark,45,ApS KBUS
+238,568,23,575,dk,Denmark,45,Banedanmark
+238,568,28,655,dk,Denmark,45,CoolTEL ApS
+238,568,06,111,dk,Denmark,45,H3G
+238,568,12,303,dk,Denmark,45,Lycamobile Ltd
+238,568,03,63,dk,Denmark,45,Mach Connectivity ApS
+238,568,07,127,dk,Denmark,45,Mundio Mobile
+238,568,04,79,dk,Denmark,45,NextGen Mobile Ltd (CardBoardFish)
+238,568,10,271,dk,Denmark,45,TDC Denmark
+238,568,01,31,dk,Denmark,45,TDC Denmark
+238,568,02,47,dk,Denmark,45,Telenor/Sonofon
+238,568,77,1919,dk,Denmark,45,Telenor/Sonofon
+238,568,20,527,dk,Denmark,45,Telia
+238,568,30,783,dk,Denmark,45,Telia
+638,1592,01,31,dj,Djibouti,253,Djibouti Telecom SA (Evatis)
+366,870,110,272,dm,Dominica,1767,C & W
+366,870,020,32,dm,Dominica,1767,Cingular Wireless/Digicel
+366,870,050,80,dm,Dominica,1767,Wireless Ventures (Dominica) Ltd (Digicel Dominica)
+370,880,02,47,do,Dominican Republic,1809,Claro
+370,880,01,31,do,Dominican Republic,1809,Orange
+370,880,03,63,do,Dominican Republic,1809,TRIcom
+370,880,04,79,do,Dominican Republic,1809,Trilogy Dominicana S. A.
+740,1856,02,47,ec,Ecuador,593,Alegro/Telcsa
+740,1856,00,15,ec,Ecuador,593,MOVISTAR/OteCel
+740,1856,01,31,ec,Ecuador,593,Claro/Porta
+602,1538,01,31,eg,Egypt,20,Orange/Mobinil
+602,1538,03,63,eg,Egypt,20,ETISALAT
+602,1538,02,47,eg,Egypt,20,Vodafone/Mirsfone
+706,1798,01,31,sv,El Salvador,503,CLARO/CTE
+706,1798,02,47,sv,El Salvador,503,Digicel
+706,1798,05,95,sv,El Salvador,503,INTELFON SA de CV
+706,1798,04,79,sv,El Salvador,503,Telefonica
+706,1798,03,63,sv,El Salvador,503,Telemovil
+627,1575,03,63,gq,Equatorial Guinea,240,HiTs-GE
+627,1575,01,31,gq,Equatorial Guinea,240,ORANGE/GETESA
+657,1623,01,31,er,Eritrea,291,Eritel
+248,584,01,31,ee,Estonia,372,EMT GSM
+248,584,02,47,ee,Estonia,372,Radiolinja Eesti
+248,584,03,63,ee,Estonia,372,Tele2 Eesti AS
+248,584,04,79,ee,Estonia,372,Top Connect OU
+636,1590,01,31,et,Ethiopia,251,ETH/MTN
+750,1872,001,1,fk,Falkland Islands (Malvinas),500,Cable and Wireless South Atlantic Ltd (Falkland Islands
+288,648,03,63,fo,Faroe Islands,298,Edge Mobile Sp/F
+288,648,01,31,fo,Faroe Islands,298,Faroese Telecom
+288,648,02,47,fo,Faroe Islands,298,Kall GSM
+542,1346,02,47,fj,Fiji,679,DigiCell
+542,1346,01,31,fj,Fiji,679,Vodafone
+244,580,14,335,fi,Finland,358,Alands
+244,580,26,623,fi,Finland,358,Compatel Ltd
+244,580,03,63,fi,Finland,358,DNA/Finnet
+244,580,12,303,fi,Finland,358,DNA/Finnet
+244,580,13,319,fi,Finland,358,DNA/Finnet
+244,580,04,79,fi,Finland,358,DNA/Finnet
+244,580,21,543,fi,Finland,358,Elisa/Saunalahti
+244,580,05,95,fi,Finland,358,Elisa/Saunalahti
+244,580,82,2095,fi,Finland,358,ID-Mobile
+244,580,11,287,fi,Finland,358,Mundio Mobile (Finland) Ltd
+244,580,09,159,fi,Finland,358,Nokia Oyj
+244,580,10,271,fi,Finland,358,TDC Oy Finland
+244,580,91,2335,fi,Finland,358,TeliaSonera
+208,520,27,639,fr,France,33,AFONE SA
+208,520,92,2351,fr,France,33,Association Plate-forme Telecom
+208,520,28,655,fr,France,33,Astrium
+208,520,88,2191,fr,France,33,Bouygues Telecom
+208,520,21,543,fr,France,33,Bouygues Telecom
+208,520,20,527,fr,France,33,Bouygues Telecom
+208,520,14,335,fr,France,33,Lliad/FREE Mobile
+208,520,07,127,fr,France,33,GlobalStar
+208,520,06,111,fr,France,33,GlobalStar
+208,520,05,95,fr,France,33,GlobalStar
+208,520,29,671,fr,France,33,Orange
+208,520,17,383,fr,France,33,Legos - Local Exchange Global Operation Services SA
+208,520,16,367,fr,France,33,Lliad/FREE Mobile
+208,520,15,351,fr,France,33,Lliad/FREE Mobile
+208,520,25,607,fr,France,33,Lycamobile SARL
+208,520,24,591,fr,France,33,MobiquiThings
+208,520,03,63,fr,France,33,MobiquiThings
+208,520,31,799,fr,France,33,Mundio Mobile (France) Ltd
+208,520,26,623,fr,France,33,NRJ
+208,520,23,575,fr,France,33,Virgin Mobile/Omer
+208,520,89,2207,fr,France,33,Virgin Mobile/Omer
+208,520,91,2335,fr,France,33,Orange
+208,520,02,47,fr,France,33,Orange
+208,520,01,31,fr,France,33,Orange
+208,520,13,319,fr,France,33,S.F.R.
+208,520,11,287,fr,France,33,S.F.R.
+208,520,10,271,fr,France,33,S.F.R.
+208,520,09,159,fr,France,33,S.F.R.
+208,520,04,79,fr,France,33,SISTEER
+208,520,00,15,fr,France,33,Tel/Tel
+208,520,22,559,fr,France,33,Transatel SA
+340,832,20,527,fg,French Guiana,594,Bouygues/DigiCel
+340,832,01,31,fg,French Guiana,594,Orange Caribe
+340,832,02,47,fg,French Guiana,594,Outremer Telecom
+340,832,11,287,fg,French Guiana,594,TelCell GSM
+340,832,03,63,fg,French Guiana,594,TelCell GSM
+547,1351,15,351,pf,French Polynesia,689,Pacific Mobile Telecom (PMT)
+547,1351,20,527,pf,French Polynesia,689,Vini/Tikiphone
+628,1576,04,79,ga,Gabon,241,Azur/Usan S.A.
+628,1576,01,31,ga,Gabon,241,Libertis S.A.
+628,1576,02,47,ga,Gabon,241,MOOV/Telecel
+628,1576,03,63,ga,Gabon,241,Airtel/ZAIN/Celtel Gabon S.A.
+607,1543,02,47,gm,Gambia,220,Africel
+607,1543,03,63,gm,Gambia,220,Comium
+607,1543,01,31,gm,Gambia,220,Gamcel
+607,1543,04,79,gm,Gambia,220,Q-Cell
+282,642,01,31,ge,Georgia,995,Geocell Ltd.
+282,642,03,63,ge,Georgia,995,Iberiatel Ltd.
+282,642,02,47,ge,Georgia,995,Magti GSM Ltd.
+282,642,04,79,ge,Georgia,995,MobiTel/Beeline
+282,642,05,95,ge,Georgia,995,Silknet
+262,610,17,383,de,Germany,49,E-Plus
+262,610,10,271,de,Germany,49,DB Netz AG
+262,610,n/a,271,de,Germany,49,Debitel
+262,610,77,1919,de,Germany,49,E-Plus
+262,610,03,63,de,Germany,49,E-Plus
+262,610,05,95,de,Germany,49,E-Plus
+262,610,12,303,de,Germany,49,E-Plus
+262,610,20,527,de,Germany,49,E-Plus
+262,610,14,335,de,Germany,49,Group 3G UMTS
+262,610,43,1087,de,Germany,49,Lycamobile
+262,610,13,319,de,Germany,49,Mobilcom
+262,610,08,143,de,Germany,49,O2
+262,610,07,127,de,Germany,49,O2
+262,610,11,287,de,Germany,49,O2
+262,610,n/a,287,de,Germany,49,Talkline
+262,610,06,111,de,Germany,49,T-mobile/Telekom
+262,610,01,31,de,Germany,49,T-mobile/Telekom
+262,610,16,367,de,Germany,49,Telogic/ViStream
+262,610,42,1071,de,Germany,49,Vodafone D2
+262,610,04,79,de,Germany,49,Vodafone D2
+262,610,02,47,de,Germany,49,Vodafone D2
+262,610,09,159,de,Germany,49,Vodafone D2
+620,1568,04,79,gh,Ghana,233,Expresso Ghana Ltd
+620,1568,07,127,gh,Ghana,233,GloMobile
+620,1568,03,63,gh,Ghana,233,Milicom/Tigo
+620,1568,01,31,gh,Ghana,233,MTN
+620,1568,02,47,gh,Ghana,233,Vodafone
+620,1568,06,111,gh,Ghana,233,Airtel/ZAIN
+266,614,06,111,gi,Gibraltar,350,CTS Mobile
+266,614,09,159,gi,Gibraltar,350,eazi telecom
+266,614,01,31,gi,Gibraltar,350,Gibtel GSM
+202,514,07,127,gr,Greece,30,AMD Telecom SA
+202,514,02,47,gr,Greece,30,Cosmote
+202,514,01,31,gr,Greece,30,Cosmote
+202,514,14,335,gr,Greece,30,CyTa Mobile
+202,514,04,79,gr,Greece,30,Organismos Sidirodromon Ellados (OSE)
+202,514,03,63,gr,Greece,30,OTE Hellenic Telecommunications Organization SA
+202,514,10,271,gr,Greece,30,Tim/Wind
+202,514,09,159,gr,Greece,30,Tim/Wind
+202,514,05,95,gr,Greece,30,Vodafone
+290,656,01,31,gl,Greenland,299,Tele Greenland
+352,850,110,272,gd,Grenada,1473,Cable & Wireless
+352,850,030,48,gd,Grenada,1473,Digicel
+352,850,050,80,gd,Grenada,1473,Digicel
+340,832,08,143,gp,Guadeloupe,590,Dauphin Telecom SU (Guadeloupe Telecom)
+340,832,10,271,gp,Guadeloupe,590,
+310,784,370,880,gu,Guam,1671,Docomo
+310,784,470,1136,gu,Guam,1671,Docomo
+310,784,140,320,gu,Guam,1671,GTA Wireless
+310,784,033,51,gu,Guam,1671,Guam Teleph. Auth.
+310,784,032,50,gu,Guam,1671,IT&E OverSeas
+311,785,250,592,gu,Guam,1671,Wave Runner LLC
+704,1796,01,31,gt,Guatemala,502,Claro
+704,1796,03,63,gt,Guatemala,502,Telefonica
+704,1796,02,47,gt,Guatemala,502,TIGO/COMCEL
+611,1553,04,79,gn,Guinea,224,MTN/Areeba
+611,1553,05,95,gn,Guinea,224,Celcom
+611,1553,03,63,gn,Guinea,224,Intercel
+611,1553,01,31,gn,Guinea,224,Orange/Sonatel/Spacetel
+611,1553,02,47,gn,Guinea,224,SotelGui
+632,1586,01,31,gw,Guinea-Bissau,245,GuineTel
+632,1586,03,63,gw,Guinea-Bissau,245,Orange
+632,1586,02,47,gw,Guinea-Bissau,245,SpaceTel
+738,1848,02,47,gy,Guyana,592,Cellink Plus
+738,1848,01,31,gy,Guyana,592,DigiCel
+372,882,01,31,ht,Haiti,509,Comcel
+372,882,02,47,ht,Haiti,509,Digicel
+372,882,03,63,ht,Haiti,509,National Telecom SA (NatCom)
+708,1800,040,64,hn,Honduras,504,Digicel
+708,1800,030,48,hn,Honduras,504,HonduTel
+708,1800,001,1,hn,Honduras,504,SERCOM/CLARO
+708,1800,002,2,hn,Honduras,504,Telefonica/CELTEL
+454,1108,28,655,hk,Hongkong China,852,China Mobile/Peoples
+454,1108,13,319,hk,Hongkong China,852,China Mobile/Peoples
+454,1108,12,303,hk,Hongkong China,852,China Mobile/Peoples
+454,1108,09,159,hk,Hongkong China,852,China Motion
+454,1108,07,127,hk,Hongkong China,852,China Unicom Ltd
+454,1108,11,287,hk,Hongkong China,852,China-HongKong Telecom Ltd (CHKTL)
+454,1108,01,31,hk,Hongkong China,852,Citic Telecom Ltd.
+454,1108,02,47,hk,Hongkong China,852,CSL Ltd.
+454,1108,00,15,hk,Hongkong China,852,CSL Ltd.
+454,1108,18,399,hk,Hongkong China,852,CSL Ltd.
+454,1108,10,271,hk,Hongkong China,852,CSL/New World PCS Ltd.
+454,1108,04,79,hk,Hongkong China,852,H3G/Hutchinson
+454,1108,03,63,hk,Hongkong China,852,H3G/Hutchinson
+454,1108,14,335,hk,Hongkong China,852,H3G/Hutchinson
+454,1108,05,95,hk,Hongkong China,852,H3G/Hutchinson
+454,1108,20,527,hk,Hongkong China,852,HKT/PCCW
+454,1108,29,671,hk,Hongkong China,852,HKT/PCCW
+454,1108,16,367,hk,Hongkong China,852,HKT/PCCW
+454,1108,19,415,hk,Hongkong China,852,HKT/PCCW
+454,1108,47,1151,hk,Hongkong China,852,shared by private TETRA systems
+454,1108,40,1039,hk,Hongkong China,852,shared by private TETRA systems
+454,1108,08,143,hk,Hongkong China,852,Truephone
+454,1108,17,383,hk,Hongkong China,852,Vodafone/SmarTone
+454,1108,15,351,hk,Hongkong China,852,Vodafone/SmarTone
+454,1108,06,111,hk,Hongkong China,852,Vodafone/SmarTone
+216,534,01,31,hu,Hungary,36,Pannon/Telenor
+216,534,30,783,hu,Hungary,36,T-mobile/Magyar
+216,534,71,1823,hu,Hungary,36,UPC Magyarorszag Kft.
+216,534,70,1807,hu,Hungary,36,Vodafone
+274,628,09,159,is,Iceland,354,Amitelo
+274,628,07,127,is,Iceland,354,IceCell
+274,628,08,143,is,Iceland,354,Siminn
+274,628,01,31,is,Iceland,354,Siminn
+274,628,11,287,is,Iceland,354,NOVA
+274,628,04,79,is,Iceland,354,VIKING/IMC
+274,628,02,47,is,Iceland,354,Vodafone/Tal hf
+274,628,05,95,is,Iceland,354,Vodafone/Tal hf
+274,628,03,63,is,Iceland,354,Vodafone/Tal hf
+404,1028,42,1071,in,India,91,Aircel
+404,1028,33,831,in,India,91,Aircel
+404,1028,29,671,in,India,91,Aircel
+404,1028,28,655,in,India,91,Aircel
+404,1028,25,607,in,India,91,Aircel
+404,1028,17,383,in,India,91,Aircel
+404,1028,01,31,in,India,91,Aircel Digilink India
+404,1028,15,351,in,India,91,Aircel Digilink India
+404,1028,60,1551,in,India,91,Aircel Digilink India
+405,1029,53,1343,in,India,91,AirTel
+404,1028,86,2159,in,India,91,Barakhamba Sales & Serv.
+404,1028,13,319,in,India,91,Barakhamba Sales & Serv.
+404,1028,74,1871,in,India,91,BSNL
+404,1028,38,911,in,India,91,BSNL
+404,1028,57,1407,in,India,91,BSNL
+404,1028,80,2063,in,India,91,BSNL
+404,1028,73,1855,in,India,91,BSNL
+404,1028,34,847,in,India,91,BSNL
+404,1028,66,1647,in,India,91,BSNL
+404,1028,55,1375,in,India,91,BSNL
+404,1028,72,1839,in,India,91,BSNL
+404,1028,77,1919,in,India,91,BSNL
+404,1028,64,1615,in,India,91,BSNL
+404,1028,54,1359,in,India,91,BSNL
+404,1028,71,1823,in,India,91,BSNL
+404,1028,76,1903,in,India,91,BSNL
+404,1028,62,1583,in,India,91,BSNL
+404,1028,53,1343,in,India,91,BSNL
+404,1028,59,1439,in,India,91,BSNL
+404,1028,75,1887,in,India,91,BSNL
+404,1028,51,1311,in,India,91,BSNL
+404,1028,58,1423,in,India,91,BSNL
+404,1028,81,2079,in,India,91,BSNL
+404,1028,10,271,in,India,91,Bharti Airtel Limited (Delhi)
+404,1028,045,69,in,India,91,Bharti Airtel Limited (Karnataka) (India)
+404,1028,79,1951,in,India,91,CellOne A&N
+404,1028,89,2207,in,India,91,Escorts Telecom Ltd.
+404,1028,88,2191,in,India,91,Escorts Telecom Ltd.
+404,1028,87,2175,in,India,91,Escorts Telecom Ltd.
+404,1028,82,2095,in,India,91,Escorts Telecom Ltd.
+404,1028,12,303,in,India,91,Escotel Mobile Communications
+404,1028,19,415,in,India,91,Escotel Mobile Communications
+404,1028,56,1391,in,India,91,Escotel Mobile Communications
+405,1029,05,95,in,India,91,Fascel Limited
+404,1028,05,95,in,India,91,Fascel
+404,1028,70,1807,in,India,91,Hexacom India
+404,1028,16,367,in,India,91,Hexcom India
+404,1028,04,79,in,India,91,Idea Cellular Ltd.
+404,1028,24,591,in,India,91,Idea Cellular Ltd.
+404,1028,22,559,in,India,91,Idea Cellular Ltd.
+404,1028,78,1935,in,India,91,Idea Cellular Ltd.
+404,1028,07,127,in,India,91,Idea Cellular Ltd.
+404,1028,69,1695,in,India,91,Mahanagar Telephone Nigam
+404,1028,68,1679,in,India,91,Mahanagar Telephone Nigam
+404,1028,83,2111,in,India,91,Reliable Internet Services
+404,1028,36,879,in,India,91,Reliance Telecom Private
+404,1028,52,1327,in,India,91,Reliance Telecom Private
+404,1028,50,1295,in,India,91,Reliance Telecom Private
+404,1028,67,1663,in,India,91,Reliance Telecom Private
+404,1028,18,399,in,India,91,Reliance Telecom Private
+404,1028,85,2143,in,India,91,Reliance Telecom Private
+404,1028,09,159,in,India,91,Reliance Telecom Private
+404,1028,41,1055,in,India,91,RPG Cellular
+404,1028,14,335,in,India,91,Spice
+404,1028,44,1103,in,India,91,Spice
+404,1028,11,287,in,India,91,Sterling Cellular Ltd.
+405,1029,034,52,in,India,91,TATA / Karnataka
+404,1028,30,783,in,India,91,Usha Martin Telecom
+510,1296,08,143,id,Indonesia,62,Axis/Natrindo
+510,1296,99,2463,id,Indonesia,62,Esia (PT Bakrie Telecom) (CDMA)
+510,1296,07,127,id,Indonesia,62,Flexi (PT Telkom) (CDMA)
+510,1296,89,2207,id,Indonesia,62,H3G CP
+510,1296,21,543,id,Indonesia,62,Indosat/Satelindo/M3
+510,1296,01,31,id,Indonesia,62,Indosat/Satelindo/M3
+510,1296,00,15,id,Indonesia,62,PT Pasifik Satelit Nusantara (PSN)
+510,1296,27,639,id,Indonesia,62,PT Sampoerna Telekomunikasi Indonesia (STI)
+510,1296,28,655,id,Indonesia,62,PT Smartfren Telecom Tbk
+510,1296,09,159,id,Indonesia,62,PT Smartfren Telecom Tbk
+510,1296,11,287,id,Indonesia,62,PT. Excelcom
+510,1296,10,271,id,Indonesia,62,Telkomsel
+901,2305,13,319,n/a,International Networks,882,Antarctica
+432,1074,19,415,ir,Iran,98,Mobile Telecommunications Company of Esfahan JV-PJS (MTCE)
+432,1074,70,1807,ir,Iran,98,MTCE
+432,1074,35,863,ir,Iran,98,MTN/IranCell
+432,1074,20,527,ir,Iran,98,Rightel
+432,1074,32,815,ir,Iran,98,Taliya
+432,1074,11,287,ir,Iran,98,MCI/TCI
+432,1074,14,335,ir,Iran,98,TKC/KFZO
+418,1048,05,95,iq,Iraq,964,Asia Cell
+418,1048,92,2351,iq,Iraq,964,Itisaluna and Kalemat
+418,1048,82,2095,iq,Iraq,964,Korek
+418,1048,40,1039,iq,Iraq,964,Korek
+418,1048,45,1119,iq,Iraq,964,Mobitel (Iraq-Kurdistan) and Moutiny
+418,1048,30,783,iq,Iraq,964,Orascom Telecom
+418,1048,20,527,iq,Iraq,964,ZAIN/Atheer/Orascom
+418,1048,08,143,iq,Iraq,964,Sanatel
+272,626,04,79,ie,Ireland,353,Access Telecom Ltd.
+272,626,09,159,ie,Ireland,353,Clever Communications Ltd
+272,626,07,127,ie,Ireland,353,eircom Ltd
+272,626,05,95,ie,Ireland,353,Three/H3G
+272,626,11,287,ie,Ireland,353,Tesco Mobile/Liffey Telecom
+272,626,13,319,ie,Ireland,353,Lycamobile
+272,626,03,63,ie,Ireland,353,Meteor Mobile Ltd.
+272,626,02,47,ie,Ireland,353,Three/O2/Digifone
+272,626,01,31,ie,Ireland,353,Vodafone Eircell
+425,1061,14,335,il,Israel,972,Alon Cellular Ltd
+425,1061,02,47,il,Israel,972,Cellcom ltd.
+425,1061,08,143,il,Israel,972,Golan Telekom
+425,1061,15,351,il,Israel,972,Home Cellular Ltd
+425,1061,77,1919,il,Israel,972,Hot Mobile/Mirs
+425,1061,07,127,il,Israel,972,Hot Mobile/Mirs
+425,1061,01,31,il,Israel,972,Orange/Partner Co. Ltd.
+425,1061,03,63,il,Israel,972,Pelephone
+425,1061,12,303,il,Israel,972,Pelephone
+425,1061,16,367,il,Israel,972,Rami Levy Hashikma Marketing Communications Ltd
+425,1061,19,415,il,Israel,972,Telzar/AZI
+222,546,34,847,it,Italy,39,BT Italia SpA
+222,546,02,47,it,Italy,39,Elsacom
+222,546,08,143,it,Italy,39,Fastweb SpA
+222,546,00,15,it,Italy,39,Fix Line
+222,546,99,2463,it,Italy,39,Hi3G
+222,546,77,1919,it,Italy,39,IPSE 2000
+222,546,35,863,it,Italy,39,Lycamobile Srl
+222,546,07,127,it,Italy,39,Noverca Italia Srl
+222,546,33,831,it,Italy,39,PosteMobile SpA
+222,546,00,15,it,Italy,39,Premium Number(s)
+222,546,30,783,it,Italy,39,RFI Rete Ferroviaria Italiana SpA
+222,546,48,1167,it,Italy,39,Telecom Italia Mobile SpA
+222,546,43,1087,it,Italy,39,Telecom Italia Mobile SpA
+222,546,01,31,it,Italy,39,TIM
+222,546,10,271,it,Italy,39,Vodafone
+222,546,06,111,it,Italy,39,Vodafone
+222,546,00,15,it,Italy,39,VOIP Line
+222,546,44,1103,it,Italy,39,WIND (Blu) -
+222,546,88,2191,it,Italy,39,WIND (Blu) -
+612,1554,07,127,ci,Ivory Coast,225,Aircomm SA
+612,1554,02,47,ci,Ivory Coast,225,Atlantik Tel./Moov
+612,1554,04,79,ci,Ivory Coast,225,Comium
+612,1554,01,31,ci,Ivory Coast,225,Comstar
+612,1554,05,95,ci,Ivory Coast,225,MTN
+612,1554,03,63,ci,Ivory Coast,225,Orange
+612,1554,06,111,ci,Ivory Coast,225,OriCell
+338,824,110,272,jm,Jamaica,1876,Cable & Wireless
+338,824,020,32,jm,Jamaica,1876,Cable & Wireless
+338,824,180,384,jm,Jamaica,1876,Cable & Wireless
+338,824,050,80,jm,Jamaica,1876,DIGICEL/Mossel
+440,1088,00,15,jp,Japan,81,Y-Mobile
+440,1088,75,1887,jp,Japan,81,KDDI Corporation
+440,1088,56,1391,jp,Japan,81,KDDI Corporation
+441,1089,70,1807,jp,Japan,81,KDDI Corporation
+440,1088,52,1327,jp,Japan,81,KDDI Corporation
+440,1088,76,1903,jp,Japan,81,KDDI Corporation
+440,1088,71,1823,jp,Japan,81,KDDI Corporation
+440,1088,53,1343,jp,Japan,81,KDDI Corporation
+440,1088,77,1919,jp,Japan,81,KDDI Corporation
+440,1088,08,143,jp,Japan,81,KDDI Corporation
+440,1088,72,1839,jp,Japan,81,KDDI Corporation
+440,1088,54,1359,jp,Japan,81,KDDI Corporation
+440,1088,79,1951,jp,Japan,81,KDDI Corporation
+440,1088,07,127,jp,Japan,81,KDDI Corporation
+440,1088,73,1855,jp,Japan,81,KDDI Corporation
+440,1088,55,1375,jp,Japan,81,KDDI Corporation
+440,1088,88,2191,jp,Japan,81,KDDI Corporation
+440,1088,50,1295,jp,Japan,81,KDDI Corporation
+440,1088,74,1871,jp,Japan,81,KDDI Corporation
+440,1088,70,1807,jp,Japan,81,KDDI Corporation
+440,1088,89,2207,jp,Japan,81,KDDI Corporation
+440,1088,51,1311,jp,Japan,81,KDDI Corporation
+440,1088,67,1663,jp,Japan,81,NTT Docomo
+440,1088,01,31,jp,Japan,81,NTT Docomo
+440,1088,14,335,jp,Japan,81,NTT Docomo
+441,1089,94,2383,jp,Japan,81,NTT Docomo
+441,1089,41,1055,jp,Japan,81,NTT Docomo
+440,1088,62,1583,jp,Japan,81,NTT Docomo
+440,1088,39,927,jp,Japan,81,NTT Docomo
+440,1088,30,783,jp,Japan,81,NTT Docomo
+440,1088,10,271,jp,Japan,81,NTT Docomo
+441,1089,45,1119,jp,Japan,81,NTT Docomo
+440,1088,24,591,jp,Japan,81,NTT Docomo
+440,1088,68,1679,jp,Japan,81,NTT Docomo
+440,1088,15,351,jp,Japan,81,NTT Docomo
+441,1089,98,2447,jp,Japan,81,NTT Docomo
+441,1089,42,1071,jp,Japan,81,NTT Docomo
+440,1088,63,1599,jp,Japan,81,NTT Docomo
+440,1088,38,911,jp,Japan,81,NTT Docomo
+440,1088,26,623,jp,Japan,81,NTT Docomo
+440,1088,11,287,jp,Japan,81,NTT Docomo
+440,1088,21,543,jp,Japan,81,NTT Docomo
+441,1089,44,1103,jp,Japan,81,NTT Docomo
+440,1088,13,319,jp,Japan,81,NTT Docomo
+440,1088,23,575,jp,Japan,81,NTT Docomo
+440,1088,69,1695,jp,Japan,81,NTT Docomo
+440,1088,16,367,jp,Japan,81,NTT Docomo
+441,1089,99,2463,jp,Japan,81,NTT Docomo
+440,1088,34,847,jp,Japan,81,NTT Docomo
+440,1088,64,1615,jp,Japan,81,NTT Docomo
+440,1088,37,895,jp,Japan,81,NTT Docomo
+440,1088,25,607,jp,Japan,81,NTT Docomo
+440,1088,22,559,jp,Japan,81,NTT Docomo
+441,1089,43,1087,jp,Japan,81,NTT Docomo
+440,1088,27,639,jp,Japan,81,NTT Docomo
+440,1088,02,47,jp,Japan,81,NTT Docomo
+440,1088,17,383,jp,Japan,81,NTT Docomo
+440,1088,31,799,jp,Japan,81,NTT Docomo
+440,1088,87,2175,jp,Japan,81,NTT Docomo
+440,1088,65,1631,jp,Japan,81,NTT Docomo
+440,1088,36,879,jp,Japan,81,NTT Docomo
+441,1089,92,2351,jp,Japan,81,NTT Docomo
+440,1088,12,303,jp,Japan,81,NTT Docomo
+440,1088,58,1423,jp,Japan,81,NTT Docomo
+440,1088,28,655,jp,Japan,81,NTT Docomo
+440,1088,03,63,jp,Japan,81,NTT Docomo
+440,1088,18,399,jp,Japan,81,NTT Docomo
+441,1089,91,2335,jp,Japan,81,NTT Docomo
+440,1088,32,815,jp,Japan,81,NTT Docomo
+440,1088,61,1567,jp,Japan,81,NTT Docomo
+440,1088,66,1647,jp,Japan,81,NTT Docomo
+440,1088,35,863,jp,Japan,81,NTT Docomo
+441,1089,93,2367,jp,Japan,81,NTT Docomo
+441,1089,40,1039,jp,Japan,81,NTT Docomo
+440,1088,49,1183,jp,Japan,81,NTT Docomo
+440,1088,29,671,jp,Japan,81,NTT Docomo
+440,1088,09,159,jp,Japan,81,NTT Docomo
+440,1088,19,415,jp,Japan,81,NTT Docomo
+441,1089,90,2319,jp,Japan,81,NTT Docomo
+440,1088,33,831,jp,Japan,81,NTT Docomo
+440,1088,60,1551,jp,Japan,81,NTT Docomo
+440,1088,99,2463,jp,Japan,81,NTT Docomo
+440,1088,78,1935,jp,Japan,81,Okinawa Cellular Telephone
+440,1088,04,79,jp,Japan,81,SoftBank Mobile Corp
+441,1089,62,1583,jp,Japan,81,SoftBank Mobile Corp
+440,1088,45,1119,jp,Japan,81,SoftBank Mobile Corp
+440,1088,20,527,jp,Japan,81,SoftBank Mobile Corp
+440,1088,96,2415,jp,Japan,81,SoftBank Mobile Corp
+440,1088,40,1039,jp,Japan,81,SoftBank Mobile Corp
+441,1089,63,1599,jp,Japan,81,SoftBank Mobile Corp
+440,1088,47,1151,jp,Japan,81,SoftBank Mobile Corp
+440,1088,95,2399,jp,Japan,81,SoftBank Mobile Corp
+440,1088,41,1055,jp,Japan,81,SoftBank Mobile Corp
+441,1089,64,1615,jp,Japan,81,SoftBank Mobile Corp
+440,1088,46,1135,jp,Japan,81,SoftBank Mobile Corp
+440,1088,97,2431,jp,Japan,81,SoftBank Mobile Corp
+440,1088,42,1071,jp,Japan,81,SoftBank Mobile Corp
+441,1089,65,1631,jp,Japan,81,SoftBank Mobile Corp
+440,1088,90,2319,jp,Japan,81,SoftBank Mobile Corp
+440,1088,92,2351,jp,Japan,81,SoftBank Mobile Corp
+440,1088,98,2447,jp,Japan,81,SoftBank Mobile Corp
+440,1088,43,1087,jp,Japan,81,SoftBank Mobile Corp
+440,1088,93,2367,jp,Japan,81,SoftBank Mobile Corp
+440,1088,48,1167,jp,Japan,81,SoftBank Mobile Corp
+440,1088,06,111,jp,Japan,81,SoftBank Mobile Corp
+441,1089,61,1567,jp,Japan,81,SoftBank Mobile Corp
+440,1088,44,1103,jp,Japan,81,SoftBank Mobile Corp
+440,1088,94,2383,jp,Japan,81,SoftBank Mobile Corp
+440,1088,85,2143,jp,Japan,81,KDDI Corporation
+440,1088,83,2111,jp,Japan,81,KDDI Corporation
+440,1088,80,2063,jp,Japan,81,KDDI Corporation
+440,1088,86,2159,jp,Japan,81,KDDI Corporation
+440,1088,81,2079,jp,Japan,81,KDDI Corporation
+440,1088,84,2127,jp,Japan,81,KDDI Corporation
+440,1088,82,2095,jp,Japan,81,KDDI Corporation
+416,1046,77,1919,jo,Jordan,962,Orange/Petra
+416,1046,03,63,jo,Jordan,962,Umniah Mobile Co.
+416,1046,02,47,jo,Jordan,962,Xpress
+416,1046,01,31,jo,Jordan,962,ZAIN /J.M.T.S
+401,1025,01,31,kz,Kazakhstan,7,Beeline/KaR-Tel LLP
+401,1025,07,127,kz,Kazakhstan,7,Dalacom/Altel
+401,1025,02,47,kz,Kazakhstan,7,K-Cell
+401,1025,77,1919,kz,Kazakhstan,7,Tele2/NEO/MTS
+639,1593,05,95,ke,Kenya,254,Econet Wireless
+639,1593,07,127,ke,Kenya,254,Orange
+639,1593,02,47,ke,Kenya,254,Safaricom Ltd.
+639,1593,03,63,ke,Kenya,254,Airtel/Zain/Celtel Ltd.
+545,1349,09,159,ki,Kiribati,686,Kiribati Frigate
+467,1127,193,403,kp,Korea N. Dem. People's Rep.,850,Sun Net
+450,1104,02,47,kr,Korea S Republic of,82,KT Freetel Co. Ltd.
+450,1104,04,79,kr,Korea S Republic of,82,KT Freetel Co. Ltd.
+450,1104,08,143,kr,Korea S Republic of,82,KT Freetel Co. Ltd.
+450,1104,06,111,kr,Korea S Republic of,82,LG Telecom
+450,1104,03,63,kr,Korea S Republic of,82,SK Telecom
+450,1104,05,95,kr,Korea S Republic of,82,SK Telecom Co. Ltd
+419,1049,04,79,kw,Kuwait,965,Viva
+419,1049,03,63,kw,Kuwait,965,Wataniya
+419,1049,02,47,kw,Kuwait,965,Zain
+437,1079,03,63,kg,Kyrgyzstan,996,AkTel LLC
+437,1079,01,31,kg,Kyrgyzstan,996,Beeline/Bitel
+437,1079,05,95,kg,Kyrgyzstan,996,MEGACOM
+437,1079,09,159,kg,Kyrgyzstan,996,O!/NUR Telecom
+457,1111,02,47,la,Laos P.D.R.,856,ETL Mobile
+457,1111,01,31,la,Laos P.D.R.,856,Lao Tel
+457,1111,08,143,la,Laos P.D.R.,856,Beeline/Tigo/Millicom
+457,1111,03,63,la,Laos P.D.R.,856,UNITEL/LAT
+247,583,05,95,lv,Latvia,371,Bite
+247,583,01,31,lv,Latvia,371,Latvian Mobile Phone
+247,583,09,159,lv,Latvia,371,SIA Camel Mobile
+247,583,08,143,lv,Latvia,371,SIA IZZI
+247,583,07,127,lv,Latvia,371,SIA Master Telecom
+247,583,06,111,lv,Latvia,371,SIA Rigatta
+247,583,02,47,lv,Latvia,371,Tele2
+247,583,03,63,lv,Latvia,371,TRIATEL/Telekom Baltija
+415,1045,33,831,lb,Lebanon,961,Cellis
+415,1045,32,815,lb,Lebanon,961,Cellis
+415,1045,35,863,lb,Lebanon,961,Cellis
+415,1045,34,847,lb,Lebanon,961,FTML Cellis
+415,1045,39,927,lb,Lebanon,961,MIC2/LibanCell/MTC
+415,1045,38,911,lb,Lebanon,961,MIC2/LibanCell/MTC
+415,1045,37,895,lb,Lebanon,961,MIC2/LibanCell/MTC
+415,1045,01,31,lb,Lebanon,961,MIC1 (Alfa)
+415,1045,03,63,lb,Lebanon,961,MIC2/LibanCell/MTC
+415,1045,36,879,lb,Lebanon,961,MIC2/LibanCell/MTC
+651,1617,02,47,ls,Lesotho,266,Econet/Ezi-cel
+651,1617,01,31,ls,Lesotho,266,Vodacom Lesotho
+618,1560,07,127,lr,Liberia,231,CELLCOM
+618,1560,04,79,lr,Liberia,231,Comium BVI
+618,1560,02,47,lr,Liberia,231,Libercell
+618,1560,20,527,lr,Liberia,231,LibTelco
+618,1560,01,31,lr,Liberia,231,Lonestar
+606,1542,02,47,ly,Libya,218,Al-Madar
+606,1542,01,31,ly,Libya,218,Al-Madar
+606,1542,06,111,ly,Libya,218,Hatef
+606,1542,00,15,ly,Libya,218,Libyana
+606,1542,03,63,ly,Libya,218,Libyana
+295,661,06,111,li,Liechtenstein,423,CUBIC (Liechtenstein
+295,661,07,127,li,Liechtenstein,423,First Mobile AG
+295,661,02,47,li,Liechtenstein,423,Orange
+295,661,01,31,li,Liechtenstein,423,Swisscom FL AG
+295,661,77,1919,li,Liechtenstein,423,Alpmobile/Tele2
+295,661,05,95,li,Liechtenstein,423,Telecom FL1 AG
+246,582,02,47,lt,Lithuania,370,Bite
+246,582,01,31,lt,Lithuania,370,Omnitel
+246,582,03,63,lt,Lithuania,370,Tele2
+270,624,77,1919,lu,Luxembourg,352,Millicom Tango GSM
+270,624,01,31,lu,Luxembourg,352,P+T/Post LUXGSM
+270,624,99,2463,lu,Luxembourg,352,Orange/VOXmobile S.A.
+455,1109,01,31,mo,Macao China,853,C.T.M. TELEMOVEL+
+455,1109,04,79,mo,Macao China,853,C.T.M. TELEMOVEL+
+455,1109,02,47,mo,Macao China,853,China Telecom
+455,1109,05,95,mo,Macao China,853,Hutchison Telephone Co. Ltd
+455,1109,03,63,mo,Macao China,853,Hutchison Telephone Co. Ltd
+455,1109,06,111,mo,Macao China,853,Smartone Mobile
+455,1109,00,15,mo,Macao China,853,Smartone Mobile
+294,660,75,1887,mk,Macedonia,389,ONE/Cosmofone
+294,660,02,47,mk,Macedonia,389,ONE/Cosmofone
+294,660,01,31,mk,Macedonia,389,T-Mobile/Mobimak
+294,660,03,63,mk,Macedonia,389,VIP Mobile
+646,1606,01,31,mg,Madagascar,261,Airtel/MADACOM
+646,1606,02,47,mg,Madagascar,261,Orange/Soci
+646,1606,03,63,mg,Madagascar,261,Sacel
+646,1606,04,79,mg,Madagascar,261,Telma
+650,1616,01,31,mw,Malawi,265,TNM/Telekom Network Ltd.
+650,1616,10,271,mw,Malawi,265,Airtel/Zain/Celtel ltd.
+502,1282,01,31,my,Malaysia,60,Art900
+502,1282,151,337,my,Malaysia,60,Baraka Telecom Sdn Bhd
+502,1282,19,415,my,Malaysia,60,CelCom
+502,1282,13,319,my,Malaysia,60,CelCom
+502,1282,198,408,my,Malaysia,60,CelCom
+502,1282,16,367,my,Malaysia,60,Digi Telecommunications
+502,1282,10,271,my,Malaysia,60,Digi Telecommunications
+502,1282,20,527,my,Malaysia,60,Electcoms Wireless Sdn Bhd
+502,1282,17,383,my,Malaysia,60,Maxis
+502,1282,12,303,my,Malaysia,60,Maxis
+502,1282,11,287,my,Malaysia,60,MTX Utara
+502,1282,153,339,my,Malaysia,60,Webe/Packet One Networks (Malaysia) Sdn Bhd
+502,1282,155,341,my,Malaysia,60,Samata Communications Sdn Bhd
+502,1282,154,340,my,Malaysia,60,Tron/Talk Focus Sdn Bhd
+502,1282,18,399,my,Malaysia,60,U Mobile
+502,1282,195,405,my,Malaysia,60,XOX Com Sdn Bhd
+502,1282,152,338,my,Malaysia,60,YES
+472,1138,01,31,mv,Maldives,960,Dhiraagu/C&W
+472,1138,02,47,mv,Maldives,960,Ooredo/Wataniya
+610,1552,01,31,ml,Mali,223,Malitel
+610,1552,02,47,ml,Mali,223,Orange/IKATEL
+278,632,21,543,mt,Malta,356,GO Mobile
+278,632,77,1919,mt,Malta,356,Melita
+278,632,01,31,mt,Malta,356,Vodafone
+340,832,12,303,mq,Martinique (French Department of),596,UTS Caraibe
+609,1545,02,47,mr,Mauritania,222,Chinguitel SA
+609,1545,01,31,mr,Mauritania,222,Mattel
+609,1545,10,271,mr,Mauritania,222,Mauritel
+617,1559,10,271,mu,Mauritius,230,Emtel Ltd
+617,1559,03,63,mu,Mauritius,230,Mahanagar Telephone
+617,1559,02,47,mu,Mauritius,230,Mahanagar Telephone
+617,1559,01,31,mu,Mauritius,230,Orange/Cellplus
+334,820,50,1295,mx,Mexico,52,AT&T/IUSACell
+334,820,050,80,mx,Mexico,52,AT&T/IUSACell
+334,820,040,64,mx,Mexico,52,AT&T/IUSACell
+334,820,04,79,mx,Mexico,52,AT&T/IUSACell
+334,820,030,48,mx,Mexico,52,Movistar/Pegaso
+334,820,03,63,mx,Mexico,52,Movistar/Pegaso
+334,820,01,31,mx,Mexico,52,NEXTEL
+334,820,09,159,mx,Mexico,52,NEXTEL
+334,820,090,144,mx,Mexico,52,NEXTEL
+334,820,010,16,mx,Mexico,52,NEXTEL
+334,820,080,128,mx,Mexico,52,Operadora Unefon SA de CV
+334,820,070,112,mx,Mexico,52,Operadora Unefon SA de CV
+334,820,060,96,mx,Mexico,52,SAI PCS
+334,820,020,32,mx,Mexico,52,TelCel/America Movil
+334,820,02,47,mx,Mexico,52,TelCel/America Movil
+550,1360,01,31,fm,Micronesia,691,FSM Telecom
+259,601,04,79,md,Moldova,373,Eventis Mobile
+259,601,05,95,md,Moldova,373,IDC/Unite
+259,601,99,2463,md,Moldova,373,IDC/Unite
+259,601,03,63,md,Moldova,373,IDC/Unite
+259,601,02,47,md,Moldova,373,Moldcell
+259,601,01,31,md,Moldova,373,Orange/Voxtel
+212,530,10,271,mc,Monaco,377,Monaco Telecom
+212,530,01,31,mc,Monaco,377,Monaco Telecom
+428,1064,98,2447,mn,Mongolia,976,G-Mobile Corporation Ltd
+428,1064,99,2463,mn,Mongolia,976,Mobicom
+428,1064,91,2335,mn,Mongolia,976,Skytel Co. Ltd
+428,1064,00,15,mn,Mongolia,976,Skytel Co. Ltd
+428,1064,88,2191,mn,Mongolia,976,Unitel
+297,663,02,47,me,Montenegro,382,Monet/T-mobile
+297,663,03,63,me,Montenegro,382,Mtel
+297,663,01,31,me,Montenegro,382,Telenor/Promonte GSM
+354,852,860,2144,ms,Montserrat,1664,Cable & Wireless
+604,1540,01,31,ma,Morocco,212,IAM/Itissallat
+604,1540,02,47,ma,Morocco,212,INWI/WANA
+604,1540,00,15,ma,Morocco,212,Medi Telecom
+643,1603,01,31,mz,Mozambique,258,mCel
+643,1603,03,63,mz,Mozambique,258,Movitel
+643,1603,04,79,mz,Mozambique,258,Vodacom
+414,1044,01,31,mm,Myanmar (Burma),95,Myanmar Post & Teleco.
+414,1044,05,95,mm,Myanmar (Burma),95,Oreedoo
+414,1044,06,111,mm,Myanmar (Burma),95,Telenor
+649,1609,03,63,na,Namibia,264,Leo / Orascom
+649,1609,01,31,na,Namibia,264,MTC
+649,1609,02,47,na,Namibia,264,Switch/Nam. Telec.
+429,1065,02,47,np,Nepal,977,Ncell
+429,1065,01,31,np,Nepal,977,NT Mobile / Namaste
+429,1065,04,79,np,Nepal,977,Smart Cell
+204,516,14,335,nl,Netherlands,31,6GMOBILE BV
+204,516,23,575,nl,Netherlands,31,Aspider Solutions
+204,516,05,95,nl,Netherlands,31,Elephant Talk Communications Premium Rate Services Netherlands BV
+204,516,17,383,nl,Netherlands,31,Intercity Mobile Communications BV
+204,516,08,143,nl,Netherlands,31,KPN Telecom B.V.
+204,516,69,1695,nl,Netherlands,31,KPN Telecom B.V.
+204,516,10,271,nl,Netherlands,31,KPN Telecom B.V.
+204,516,12,303,nl,Netherlands,31,KPN/Telfort
+204,516,28,655,nl,Netherlands,31,Lancelot BV
+204,516,09,159,nl,Netherlands,31,Lycamobile Ltd
+204,516,06,111,nl,Netherlands,31,Mundio/Vectone Mobile
+204,516,21,543,nl,Netherlands,31,NS Railinfrabeheer B.V.
+204,516,24,591,nl,Netherlands,31,Private Mobility Nederland BV
+204,516,98,2447,nl,Netherlands,31,T-Mobile B.V.
+204,516,16,367,nl,Netherlands,31,T-Mobile B.V.
+204,516,20,527,nl,Netherlands,31,T-mobile/former Orange
+204,516,02,47,nl,Netherlands,31,Tele2
+204,516,07,127,nl,Netherlands,31,Teleena Holding BV
+204,516,68,1679,nl,Netherlands,31,Unify Mobile
+204,516,18,399,nl,Netherlands,31,UPC Nederland BV
+204,516,04,79,nl,Netherlands,31,Vodafone Libertel
+204,516,03,63,nl,Netherlands,31,Voiceworks Mobile BV
+204,516,15,351,nl,Netherlands,31,Ziggo BV
+362,866,630,1584,an,Netherlands Antilles,599,Cingular Wireless
+362,866,51,1311,an,Netherlands Antilles,599,TELCELL GSM
+362,866,91,2335,an,Netherlands Antilles,599,SETEL GSM
+362,866,951,2385,an,Netherlands Antilles,599,UTS Wireless
+546,1350,01,31,nc,New Caledonia,687,OPT Mobilis
+530,1328,28,655,nz,New Zealand,64,2degrees
+530,1328,05,95,nz,New Zealand,64,Spark/NZ Telecom
+530,1328,02,47,nz,New Zealand,64,Spark/NZ Telecom
+530,1328,04,79,nz,New Zealand,64,Telstra
+530,1328,24,591,nz,New Zealand,64,Two Degrees Mobile Ltd
+530,1328,01,31,nz,New Zealand,64,Vodafone
+530,1328,03,63,nz,New Zealand,64,Walker Wireless Ltd.
+710,1808,21,543,ni,Nicaragua,505,Empresa Nicaraguense de Telecomunicaciones SA (ENITEL)
+710,1808,30,783,ni,Nicaragua,505,Movistar
+710,1808,73,1855,ni,Nicaragua,505,Claro
+614,1556,03,63,ne,Niger,227,MOOV/TeleCel
+614,1556,04,79,ne,Niger,227,Orange/Sahelc.
+614,1556,01,31,ne,Niger,227,Orange/Sahelc.
+614,1556,02,47,ne,Niger,227,Airtel/Zain/CelTel
+621,1569,20,527,ng,Nigeria,234,Airtel/ZAIN/Econet
+621,1569,60,1551,ng,Nigeria,234,ETISALAT
+621,1569,50,1295,ng,Nigeria,234,Glo Mobile
+621,1569,40,1039,ng,Nigeria,234,M-Tel/Nigeria Telecom. Ltd.
+621,1569,30,783,ng,Nigeria,234,MTN
+621,1569,99,2463,ng,Nigeria,234,Starcomms
+621,1569,25,607,ng,Nigeria,234,Visafone
+621,1569,01,31,ng,Nigeria,234,Visafone
+555,1365,01,31,nu,Niue,683,Niue Telecom
+242,578,09,159,no,Norway,47,Com4 AS
+242,578,14,335,no,Norway,47,ICE Nordisk Mobiltelefon AS
+242,578,21,543,no,Norway,47,Jernbaneverket (GSM-R)
+242,578,20,527,no,Norway,47,Jernbaneverket (GSM-R)
+242,578,23,575,no,Norway,47,Lycamobile Ltd
+242,578,02,47,no,Norway,47,Netcom
+242,578,22,559,no,Norway,47,Network Norway AS
+242,578,05,95,no,Norway,47,Network Norway AS
+242,578,06,111,no,Norway,47,ICE Nordisk Mobiltelefon AS
+242,578,08,143,no,Norway,47,TDC Mobil A/S
+242,578,04,79,no,Norway,47,Tele2
+242,578,01,31,no,Norway,47,Telenor
+242,578,12,303,no,Norway,47,Telenor
+242,578,03,63,no,Norway,47,Teletopia
+242,578,07,127,no,Norway,47,Ventelo AS
+242,578,017,23,no,Norway,47,Ventelo AS
+422,1058,03,63,om,Oman,968,Nawras
+422,1058,02,47,om,Oman,968,Oman Mobile/GTO
+410,1040,08,143,pk,Pakistan,92,Instaphone
+410,1040,01,31,pk,Pakistan,92,Mobilink
+410,1040,06,111,pk,Pakistan,92,Telenor
+410,1040,03,63,pk,Pakistan,92,UFONE/PAKTel
+410,1040,07,127,pk,Pakistan,92,Warid Telecom
+410,1040,04,79,pk,Pakistan,92,ZONG/CMPak
+552,1362,80,2063,pw,Palau (Republic of),680,Palau Mobile Corp. (PMC) (Palau
+552,1362,01,31,pw,Palau (Republic of),680,Palau National Communications Corp. (PNCC) (Palau
+425,1061,05,95,ps,Palestinian Territory,970,Jawwal
+425,1061,06,111,ps,Palestinian Territory,970,Wataniya Mobile
+714,1812,01,31,pa,Panama,507,Cable & W./Mas Movil
+714,1812,03,63,pa,Panama,507,Claro
+714,1812,04,79,pa,Panama,507,Digicel
+714,1812,02,47,pa,Panama,507,Movistar
+714,1812,020,32,pa,Panama,507,Movistar
+537,1335,03,63,pg,Papua New Guinea,675,Digicel
+537,1335,02,47,pg,Papua New Guinea,675,GreenCom PNG Ltd
+537,1335,01,31,pg,Papua New Guinea,675,Pacific Mobile
+744,1860,02,47,py,Paraguay,595,Claro/Hutchison
+744,1860,03,63,py,Paraguay,595,Compa
+744,1860,01,31,py,Paraguay,595,Hola/VOX
+744,1860,05,95,py,Paraguay,595,TIM/Nucleo/Personal
+744,1860,04,79,py,Paraguay,595,Tigo/Telecel
+716,1814,20,527,pe,Peru,51,Claro /Amer.Mov./TIM
+716,1814,10,271,pe,Peru,51,Claro /Amer.Mov./TIM
+716,1814,02,47,pe,Peru,51,GlobalStar
+716,1814,01,31,pe,Peru,51,GlobalStar
+716,1814,06,111,pe,Peru,51,Movistar
+716,1814,17,383,pe,Peru,51,Nextel
+716,1814,07,127,pe,Peru,51,Nextel
+716,1814,15,351,pe,Peru,51,Viettel Mobile
+515,1301,00,15,ph,Philippines,63,Fix Line
+515,1301,01,31,ph,Philippines,63,Globe Telecom
+515,1301,02,47,ph,Philippines,63,Globe Telecom
+515,1301,88,2191,ph,Philippines,63,Next Mobile
+515,1301,18,399,ph,Philippines,63,RED Mobile/Cure
+515,1301,03,63,ph,Philippines,63,Smart
+515,1301,05,95,ph,Philippines,63,SUN/Digitel
+260,608,17,383,pl,Poland,48,Aero2 SP.
+260,608,18,399,pl,Poland,48,AMD Telecom.
+260,608,38,911,pl,Poland,48,CallFreedom Sp. z o.o.
+260,608,12,303,pl,Poland,48,Cyfrowy POLSAT S.A.
+260,608,08,143,pl,Poland,48,e-Telko
+260,608,09,159,pl,Poland,48,Lycamobile
+260,608,16,367,pl,Poland,48,Mobyland
+260,608,36,879,pl,Poland,48,Mundio Mobile Sp. z o.o.
+260,608,07,127,pl,Poland,48,Play/P4
+260,608,11,287,pl,Poland,48,NORDISK Polska
+260,608,05,95,pl,Poland,48,Orange/IDEA/Centertel
+260,608,03,63,pl,Poland,48,Orange/IDEA/Centertel
+260,608,35,863,pl,Poland,48,PKP Polskie Linie Kolejowe S.A.
+260,608,98,2447,pl,Poland,48,Play/P4
+260,608,06,111,pl,Poland,48,Play/P4
+260,608,01,31,pl,Poland,48,Polkomtel/Plus
+260,608,14,335,pl,Poland,48,Sferia
+260,608,13,319,pl,Poland,48,Sferia
+260,608,10,271,pl,Poland,48,Sferia
+260,608,34,847,pl,Poland,48,T-Mobile/ERA
+260,608,02,47,pl,Poland,48,T-Mobile/ERA
+260,608,15,351,pl,Poland,48,Tele2
+260,608,04,79,pl,Poland,48,Tele2
+268,616,04,79,pt,Portugal,351,Lycamobile
+268,616,07,127,pt,Portugal,351,NOS/Optimus
+268,616,03,63,pt,Portugal,351,NOS/Optimus
+268,616,06,111,pt,Portugal,351,MEO/TMN
+268,616,01,31,pt,Portugal,351,Vodafone
+330,816,110,272,pr,Puerto Rico,,Puerto Rico Telephone Company Inc. (PRTC)
+330,816,11,287,pr,Puerto Rico,,Puerto Rico Telephone Company Inc. (PRTC)
+427,1063,01,31,qa,Qatar,974,Ooredoo/Qtel
+427,1063,02,47,qa,Qatar,974,Vodafone
+647,1607,00,15,re,Reunion,262,Orange
+647,1607,02,47,re,Reunion,262,Outremer Telecom
+647,1607,10,271,re,Reunion,262,SFR
+226,550,03,63,ro,Romania,40,Cosmote
+226,550,11,287,ro,Romania,40,Enigma Systems
+226,550,16,367,ro,Romania,40,Lycamobile
+226,550,10,271,ro,Romania,40,Orange
+226,550,05,95,ro,Romania,40,RCS&RDS Digi Mobile
+226,550,02,47,ro,Romania,40,Romtelecom SA
+226,550,06,111,ro,Romania,40,Telemobil/Zapp
+226,550,01,31,ro,Romania,40,Vodafone
+226,550,04,79,ro,Romania,40,Telemobil/Zapp
+250,592,12,303,ru,Russian Federation,79,Baykal Westcom
+250,592,28,655,ru,Russian Federation,79,BeeLine/VimpelCom
+250,592,10,271,ru,Russian Federation,79,DTC/Don Telecom
+250,592,13,319,ru,Russian Federation,79,Kuban GSM
+250,592,35,863,ru,Russian Federation,79,MOTIV/LLC Ekaterinburg-2000
+250,592,02,47,ru,Russian Federation,79,Megafon
+250,592,01,31,ru,Russian Federation,79,MTS
+250,592,03,63,ru,Russian Federation,79,NCC
+250,592,16,367,ru,Russian Federation,79,NTC
+250,592,19,415,ru,Russian Federation,79,OJSC Altaysvyaz
+250,592,11,287,ru,Russian Federation,79,Orensot
+250,592,92,2351,ru,Russian Federation,79,Printelefone
+250,592,04,79,ru,Russian Federation,79,Sibchallenge
+250,592,44,1103,ru,Russian Federation,79,StavTelesot
+250,592,20,527,ru,Russian Federation,79,Tele2/ECC/Volgogr.
+250,592,93,2367,ru,Russian Federation,79,Telecom XXL
+250,592,39,927,ru,Russian Federation,79,UralTel
+250,592,17,383,ru,Russian Federation,79,UralTel
+250,592,99,2463,ru,Russian Federation,79,BeeLine/VimpelCom
+250,592,05,95,ru,Russian Federation,79,Yenisey Telecom
+250,592,15,351,ru,Russian Federation,79,ZAO SMARTS
+250,592,07,127,ru,Russian Federation,79,ZAO SMARTS
+635,1589,14,335,rw,Rwanda,250,Airtel
+635,1589,10,271,rw,Rwanda,250,MTN/Rwandacell
+635,1589,13,319,rw,Rwanda,250,TIGO
+356,854,110,272,kn,Saint Kitts and Nevis,1869,Cable & Wireless
+356,854,50,1295,kn,Saint Kitts and Nevis,1869,Digicel
+356,854,70,1807,kn,Saint Kitts and Nevis,1869,UTS Cariglobe
+358,856,110,272,lc,Saint Lucia,1758,Cable & Wireless
+358,856,30,783,lc,Saint Lucia,1758,Cingular Wireless
+358,856,50,1295,lc,Saint Lucia,1758,Digicel (St Lucia) Limited
+549,1353,27,639,ws,Samoa,685,Samoatel Mobile
+549,1353,01,31,ws,Samoa,685,Telecom Samoa Cellular Ltd.
+292,658,01,31,sm,San Marino,378,Prima Telecom
+626,1574,01,31,st,Sao Tome & Principe,239,CSTmovel
+901,2305,14,335,n/a,Satellite Networks,870,AeroMobile
+901,2305,11,287,n/a,Satellite Networks,870,InMarSAT
+901,2305,12,303,n/a,Satellite Networks,870,Maritime Communications Partner AS
+901,2305,05,95,n/a,Satellite Networks,870,Thuraya Satellite
+420,1056,07,127,sa,Saudi Arabia,966,Zain
+420,1056,03,63,sa,Saudi Arabia,966,Etihad/Etisalat/Mobily
+420,1056,06,111,sa,Saudi Arabia,966,Lebara Mobile
+420,1056,01,31,sa,Saudi Arabia,966,STC/Al Jawal
+420,1056,05,95,sa,Saudi Arabia,966,Virgin Mobile
+420,1056,04,79,sa,Saudi Arabia,966,Zain
+608,1544,03,63,sn,Senegal,221,Expresso/Sudatel
+608,1544,01,31,sn,Senegal,221,Orange/Sonatel
+608,1544,02,47,sn,Senegal,221,TIGO/Sentel GSM
+220,544,03,63,rs,Serbia,381,MTS/Telekom Srbija
+220,544,01,31,rs,Serbia,381,Telenor/Mobtel
+220,544,02,47,rs,Serbia,381,Telenor/Mobtel
+220,544,05,95,rs,Serbia,381,VIP Mobile
+633,1587,10,271,sc,Seychelles,248,Airtel
+633,1587,01,31,sc,Seychelles,248,C&W
+633,1587,02,47,sc,Seychelles,248,Smartcom
+619,1561,03,63,sl,Sierra Leone,232,Africel
+619,1561,01,31,sl,Sierra Leone,232,Airtel/Zain/Celtel
+619,1561,04,79,sl,Sierra Leone,232,Comium
+619,1561,05,95,sl,Sierra Leone,232,Africel
+619,1561,02,47,sl,Sierra Leone,232,Tigo/Millicom
+619,1561,25,607,sl,Sierra Leone,232,Mobitel
+525,1317,12,303,sg,Singapore,65,GRID Communications Pte Ltd
+525,1317,03,63,sg,Singapore,65,MobileOne Ltd
+525,1317,02,47,sg,Singapore,65,Singtel
+525,1317,01,31,sg,Singapore,65,Singtel
+525,1317,07,127,sg,Singapore,65,Singtel
+525,1317,06,111,sg,Singapore,65,Starhub
+525,1317,05,95,sg,Singapore,65,Starhub
+231,561,03,63,sk,Slovakia,421,4Ka
+231,561,06,111,sk,Slovakia,421,O2
+231,561,01,31,sk,Slovakia,421,Orange
+231,561,05,95,sk,Slovakia,421,Orange
+231,561,15,351,sk,Slovakia,421,Orange
+231,561,04,79,sk,Slovakia,421,T-Mobile
+231,561,02,47,sk,Slovakia,421,T-Mobile
+231,561,99,2463,sk,Slovakia,421,Zeleznice Slovenskej republiky (ZSR)
+293,659,41,1055,si,Slovenia,386,Mobitel
+293,659,40,1039,si,Slovenia,386,SI.Mobil
+293,659,10,271,si,Slovenia,386,Slovenske zeleznice d.o.o.
+293,659,64,1615,si,Slovenia,386,T-2 d.o.o.
+293,659,70,1807,si,Slovenia,386,Telemach/TusMobil/VEGA
+540,1344,02,47,sb,Solomon Islands,677,bemobile
+540,1344,10,271,sb,Solomon Islands,677,BREEZE
+540,1344,01,31,sb,Solomon Islands,677,BREEZE
+637,1591,30,783,so,Somalia,252,Golis
+637,1591,19,415,so,Somalia,252,HorTel
+637,1591,60,1551,so,Somalia,252,Nationlink
+637,1591,10,271,so,Somalia,252,Nationlink
+637,1591,04,79,so,Somalia,252,Somafone
+637,1591,71,1823,so,Somalia,252,Somtel
+637,1591,82,2095,so,Somalia,252,Somtel
+637,1591,01,31,so,Somalia,252,Telesom
+655,1621,02,47,za,South Africa,27,8.ta
+655,1621,21,543,za,South Africa,27,Cape Town Metropolitan
+655,1621,07,127,za,South Africa,27,Cell C
+655,1621,10,271,za,South Africa,27,MTN
+655,1621,12,303,za,South Africa,27,MTN
+655,1621,06,111,za,South Africa,27,Sentech
+655,1621,01,31,za,South Africa,27,Vodacom
+655,1621,19,415,za,South Africa,27,Wireless Business Solutions (Pty) Ltd
+659,1625,03,63,ss,South Sudan (Republic of),,Gemtel Ltd (South Sudan
+659,1625,02,47,ss,South Sudan (Republic of),,MTN South Sudan (South Sudan
+659,1625,04,79,ss,South Sudan (Republic of),,Network of The World Ltd (NOW) (South Sudan
+659,1625,06,111,ss,South Sudan (Republic of),,Zain South Sudan (South Sudan
+214,532,23,575,es,Spain,34,Lycamobile SL
+214,532,22,559,es,Spain,34,Digi Spain Telecom SL
+214,532,15,351,es,Spain,34,BT Espana SAU
+214,532,18,399,es,Spain,34,Cableuropa SAU (ONO)
+214,532,08,143,es,Spain,34,Euskaltel SA
+214,532,20,527,es,Spain,34,fonYou Wireless SL
+214,532,32,815,es,Spain,34,ION Mobile
+214,532,21,543,es,Spain,34,Jazz Telecom SAU
+214,532,26,623,es,Spain,34,Lleida
+214,532,25,607,es,Spain,34,Lycamobile SL
+214,532,07,127,es,Spain,34,Movistar
+214,532,05,95,es,Spain,34,Movistar
+214,532,09,159,es,Spain,34,Orange
+214,532,03,63,es,Spain,34,Orange
+214,532,11,287,es,Spain,34,Orange
+214,532,17,383,es,Spain,34,R Cable y Telec. Galicia SA
+214,532,19,415,es,Spain,34,Simyo/KPN
+214,532,16,367,es,Spain,34,Telecable de Asturias SA
+214,532,27,639,es,Spain,34,Truphone
+214,532,01,31,es,Spain,34,Vodafone
+214,532,06,111,es,Spain,34,Vodafone Enabler Espana SL
+214,532,04,79,es,Spain,34,Yoigo
+413,1043,05,95,lk,Sri Lanka,94,Airtel
+413,1043,03,63,lk,Sri Lanka,94,Etisalat/Tigo
+413,1043,08,143,lk,Sri Lanka,94,H3G Hutchison
+413,1043,01,31,lk,Sri Lanka,94,Mobitel Ltd.
+413,1043,02,47,lk,Sri Lanka,94,MTN/Dialog
+308,776,01,31,pm,St. Pierre & Miquelon,508,Ameris
+360,864,110,272,vc,St. Vincent & Gren.,1784,C & W
+360,864,100,256,vc,St. Vincent & Gren.,1784,Cingular
+360,864,10,271,vc,St. Vincent & Gren.,1784,Cingular
+360,864,050,80,vc,St. Vincent & Gren.,1784,Digicel
+360,864,70,1807,vc,St. Vincent & Gren.,1784,Digicel
+634,1588,00,15,sd,Sudan,249,Canar Telecom
+634,1588,02,47,sd,Sudan,249,MTN
+634,1588,22,559,sd,Sudan,249,MTN
+634,1588,15,351,sd,Sudan,249,Sudani One
+634,1588,07,127,sd,Sudan,249,Sudani One
+634,1588,08,143,sd,Sudan,249,Vivacell
+634,1588,05,95,sd,Sudan,249,Vivacell
+634,1588,01,31,sd,Sudan,249,ZAIN/Mobitel
+634,1588,06,111,sd,Sudan,249,ZAIN/Mobitel
+746,1862,03,63,sr,Suriname,597,Digicel
+746,1862,01,31,sr,Suriname,597,Telesur
+746,1862,02,47,sr,Suriname,597,Telecommunicatiebedrijf Suriname (TELESUR)
+746,1862,04,79,sr,Suriname,597,UNIQA
+653,1619,10,271,sz,Swaziland,268,Swazi MTN
+653,1619,01,31,sz,Swaziland,268,SwaziTelecom
+240,576,35,863,se,Sweden,46,42 Telecom AB
+240,576,16,367,se,Sweden,46,42 Telecom AB
+240,576,26,623,se,Sweden,46,Beepsend
+240,576,30,783,se,Sweden,46,NextGen Mobile Ltd (CardBoardFish)
+240,576,28,655,se,Sweden,46,CoolTEL Aps
+240,576,25,607,se,Sweden,46,Digitel Mobile Srl
+240,576,22,559,se,Sweden,46,Eu Tel AB
+240,576,27,639,se,Sweden,46,Fogg Mobile AB
+240,576,18,399,se,Sweden,46,Generic Mobile Systems Sweden AB
+240,576,17,383,se,Sweden,46,Gotalandsnatet AB
+240,576,02,47,se,Sweden,46,H3G Access AB
+240,576,04,79,se,Sweden,46,H3G Access AB
+240,576,36,879,se,Sweden,46,ID Mobile
+240,576,23,575,se,Sweden,46,Infobip Ltd.
+240,576,11,287,se,Sweden,46,Lindholmen Science Park AB
+240,576,12,303,se,Sweden,46,Lycamobile Ltd
+240,576,29,671,se,Sweden,46,Mercury International Carrier Services
+240,576,19,415,se,Sweden,46,Mundio Mobile (Sweden) Ltd
+240,576,10,271,se,Sweden,46,Spring Mobil AB
+240,576,05,95,se,Sweden,46,Svenska UMTS-N
+240,576,14,335,se,Sweden,46,TDC Sverige AB
+240,576,07,127,se,Sweden,46,Tele2 Sverige AB
+240,576,06,111,se,Sweden,46,Telenor (Vodafone)
+240,576,24,591,se,Sweden,46,Telenor (Vodafone)
+240,576,08,143,se,Sweden,46,Telenor (Vodafone)
+240,576,01,31,se,Sweden,46,Telia Mobile
+240,576,13,319,se,Sweden,46,Ventelo Sverige AB
+240,576,20,527,se,Sweden,46,Wireless Maingate AB
+240,576,15,351,se,Sweden,46,Wireless Maingate Nordic AB
+228,552,51,1311,ch,Switzerland,41,BebbiCell AG
+228,552,09,159,ch,Switzerland,41,Comfone AG
+228,552,05,95,ch,Switzerland,41,Comfone AG
+228,552,07,127,ch,Switzerland,41,TDC Sunrise
+228,552,54,1359,ch,Switzerland,41,Lycamobile AG
+228,552,52,1327,ch,Switzerland,41,Mundio Mobile AG
+228,552,03,63,ch,Switzerland,41,Salt/Orange
+228,552,01,31,ch,Switzerland,41,Swisscom
+228,552,12,303,ch,Switzerland,41,TDC Sunrise
+228,552,02,47,ch,Switzerland,41,TDC Sunrise
+228,552,08,143,ch,Switzerland,41,TDC Sunrise
+228,552,53,1343,ch,Switzerland,41,upc cablecom GmbH
+417,1047,02,47,sy,Syrian Arab Republic,963,MTN/Spacetel
+417,1047,09,159,sy,Syrian Arab Republic,963,Syriatel Holdings
+417,1047,01,31,sy,Syrian Arab Republic,963,Syriatel Holdings
+466,1126,68,1679,tw,Taiwan,886,ACeS Taiwan - ACeS Taiwan Telecommunications Co Ltd
+466,1126,05,95,tw,Taiwan,886,Asia Pacific Telecom Co. Ltd (APT)
+466,1126,11,287,tw,Taiwan,886,Chunghwa Telecom LDM
+466,1126,92,2351,tw,Taiwan,886,Chunghwa Telecom LDM
+466,1126,01,31,tw,Taiwan,886,Far EasTone
+466,1126,07,127,tw,Taiwan,886,Far EasTone
+466,1126,06,111,tw,Taiwan,886,Far EasTone
+466,1126,02,47,tw,Taiwan,886,Far EasTone
+466,1126,03,63,tw,Taiwan,886,Far EasTone
+466,1126,10,271,tw,Taiwan,886,Global Mobile Corp.
+466,1126,56,1391,tw,Taiwan,886,International Telecom Co. Ltd (FITEL)
+466,1126,88,2191,tw,Taiwan,886,KG Telecom
+466,1126,99,2463,tw,Taiwan,886,TransAsia
+466,1126,97,2431,tw,Taiwan,886,Taiwan Cellular
+466,1126,93,2367,tw,Taiwan,886,Mobitai
+466,1126,89,2207,tw,Taiwan,886,T-Star/VIBO
+466,1126,09,159,tw,Taiwan,886,VMAX Telecom Co. Ltd
+436,1078,04,79,tk,Tajikistan,992,Babilon-M
+436,1078,05,95,tk,Tajikistan,992,Bee Line
+436,1078,02,47,tk,Tajikistan,992,CJSC Indigo Tajikistan
+436,1078,12,303,tk,Tajikistan,992,Tcell/JC Somoncom
+436,1078,03,63,tk,Tajikistan,992,MLT/TT mobile
+436,1078,01,31,tk,Tajikistan,992,Tcell/JC Somoncom
+640,1600,08,143,tz,Tanzania,255,Benson Informatics Ltd
+640,1600,06,111,tz,Tanzania,255,Dovetel (T) Ltd
+640,1600,09,159,tz,Tanzania,255,Halotel/Viettel Ltd
+640,1600,11,287,tz,Tanzania,255,Smile Communications Tanzania Ltd
+640,1600,07,127,tz,Tanzania,255,Tanzania Telecommunications Company Ltd (TTCL)
+640,1600,02,47,tz,Tanzania,255,TIGO/MIC
+640,1600,01,31,tz,Tanzania,255,Tri Telecomm. Ltd.
+640,1600,04,79,tz,Tanzania,255,Vodacom Ltd
+640,1600,05,95,tz,Tanzania,255,Airtel/ZAIN/Celtel
+640,1600,03,63,tz,Tanzania,255,Zantel/Zanzibar Telecom
+520,1312,20,527,th,Thailand,66,ACeS Thailand - ACeS Regional Services Co Ltd
+520,1312,15,351,th,Thailand,66,ACT Mobile
+520,1312,03,63,th,Thailand,66,Advanced Wireless Networks/AWN
+520,1312,01,31,th,Thailand,66,AIS/Advanced Info Service
+520,1312,23,575,th,Thailand,66,Digital Phone Co.
+520,1312,00,15,th,Thailand,66,Hutch/CAT CDMA
+520,1312,05,95,th,Thailand,66,Total Access (DTAC)
+520,1312,18,399,th,Thailand,66,Total Access (DTAC)
+520,1312,99,2463,th,Thailand,66,True Move/Orange
+520,1312,04,79,th,Thailand,66,True Move/Orange
+514,1300,01,31,tp,Timor-Leste,670,Telin/ Telkomcel
+514,1300,02,47,tp,Timor-Leste,670,Timor Telecom
+615,1557,02,47,tg,Togo,228,Telecel/MOOV
+615,1557,03,63,tg,Togo,228,Telecel/MOOV
+615,1557,01,31,tg,Togo,228,Togo Telecom/TogoCELL
+539,1337,43,1087,to,Tonga,676,Shoreline Communication
+539,1337,01,31,to,Tonga,676,Tonga Communications
+374,884,120,288,tt,Trinidad and Tobago,1868,Bmobile/TSTT
+374,884,12,303,tt,Trinidad and Tobago,1868,Bmobile/TSTT
+374,884,130,304,tt,Trinidad and Tobago,1868,Digicel
+374,884,140,320,tt,Trinidad and Tobago,1868,LaqTel Ltd.
+605,1541,01,31,tn,Tunisia,216,Orange
+605,1541,03,63,tn,Tunisia,216,Oreedo/Orascom
+605,1541,02,47,tn,Tunisia,216,TuniCell/Tunisia Telecom
+605,1541,06,111,tn,Tunisia,216,TuniCell/Tunisia Telecom
+286,646,04,79,tr,Turkey,90,AVEA/Aria
+286,646,03,63,tr,Turkey,90,AVEA/Aria
+286,646,01,31,tr,Turkey,90,Turkcell
+286,646,02,47,tr,Turkey,90,Vodafone-Telsim
+438,1080,01,31,tm,Turkmenistan,993,MTS/Barash Communication
+438,1080,02,47,tm,Turkmenistan,993,Altyn Asyr/TM-Cell
+376,886,350,848,tc,Turks and Caicos Islands,,Cable & Wireless (TCI) Ltd
+376,886,050,80,tc,Turks and Caicos Islands,,Digicel TCI Ltd
+376,886,352,850,tc,Turks and Caicos Islands,,IslandCom Communications Ltd.
+553,1363,01,31,tv,Tuvalu,,Tuvalu Telecommunication Corporation (TTC)
+641,1601,01,31,ug,Uganda,256,Airtel/Celtel
+641,1601,66,1647,ug,Uganda,256,i-Tel Ltd
+641,1601,30,783,ug,Uganda,256,K2 Telecom Ltd
+641,1601,10,271,ug,Uganda,256,MTN Ltd.
+641,1601,14,335,ug,Uganda,256,Orange
+641,1601,33,831,ug,Uganda,256,Smile Communications Uganda Ltd
+641,1601,18,399,ug,Uganda,256,Suretelecom Uganda Ltd
+641,1601,11,287,ug,Uganda,256,Uganda Telecom Ltd.
+641,1601,22,559,ug,Uganda,256,Airtel/Warid
+255,597,06,111,ua,Ukraine,380,Astelit/LIFE
+255,597,05,95,ua,Ukraine,380,Golden Telecom
+255,597,39,927,ua,Ukraine,380,Golden Telecom
+255,597,04,79,ua,Ukraine,380,Intertelecom Ltd (IT)
+255,597,67,1663,ua,Ukraine,380,KyivStar
+255,597,03,63,ua,Ukraine,380,KyivStar
+255,597,21,543,ua,Ukraine,380,Telesystems Of Ukraine CJSC (TSU)
+255,597,07,127,ua,Ukraine,380,TriMob LLC
+255,597,50,1295,ua,Ukraine,380,UMC/MTS
+255,597,02,47,ua,Ukraine,380,Beeline
+255,597,01,31,ua,Ukraine,380,UMC/MTS
+255,597,68,1679,ua,Ukraine,380,Beeline
+424,1060,03,63,ae,United Arab Emirates,971,DU
+431,1073,02,47,ae,United Arab Emirates,971,Etisalat
+424,1060,02,47,ae,United Arab Emirates,971,Etisalat
+430,1072,02,47,ae,United Arab Emirates,971,Etisalat
+234,564,03,63,gb,United Kingdom,44,Airtel/Vodafone
+234,564,77,1919,gb,United Kingdom,44,BT Group
+234,564,76,1903,gb,United Kingdom,44,BT Group
+234,564,92,2351,gb,United Kingdom,44,Cable and Wireless
+234,564,07,127,gb,United Kingdom,44,Cable and Wireless
+234,564,36,879,gb,United Kingdom,44,Cable and Wireless Isle of Man
+234,564,18,399,gb,United Kingdom,44,Cloud9/wire9 Tel.
+235,565,02,47,gb,United Kingdom,44,Everyth. Ev.wh.
+234,564,17,383,gb,United Kingdom,44,FlexTel
+234,564,55,1375,gb,United Kingdom,44,Guernsey Telecoms
+234,564,14,335,gb,United Kingdom,44,HaySystems
+234,564,94,2383,gb,United Kingdom,44,H3G Hutchinson
+234,564,20,527,gb,United Kingdom,44,H3G Hutchinson
+234,564,75,1887,gb,United Kingdom,44,Inquam Telecom Ltd
+234,564,50,1295,gb,United Kingdom,44,Jersey Telecom
+234,564,35,863,gb,United Kingdom,44,JSC Ingenicum
+234,564,26,623,gb,United Kingdom,44,Lycamobile
+234,564,58,1423,gb,United Kingdom,44,Manx Telecom
+234,564,01,31,gb,United Kingdom,44,Mapesbury C. Ltd
+234,564,28,655,gb,United Kingdom,44,Marthon Telecom
+234,564,10,271,gb,United Kingdom,44,O2 Ltd.
+234,564,02,47,gb,United Kingdom,44,O2 Ltd.
+234,564,11,287,gb,United Kingdom,44,O2 Ltd.
+234,564,08,143,gb,United Kingdom,44,OnePhone
+234,564,16,367,gb,United Kingdom,44,Opal Telecom
+234,564,34,847,gb,United Kingdom,44,Everyth. Ev.wh./Orange
+234,564,33,831,gb,United Kingdom,44,Everyth. Ev.wh./Orange
+234,564,19,415,gb,United Kingdom,44,PMN/Teleware
+234,564,12,303,gb,United Kingdom,44,Railtrack Plc
+234,564,22,559,gb,United Kingdom,44,Routotelecom
+234,564,57,1407,gb,United Kingdom,44,Sky UK Limited
+234,564,24,591,gb,United Kingdom,44,Stour Marine
+234,564,37,895,gb,United Kingdom,44,Synectiv Ltd.
+234,564,30,783,gb,United Kingdom,44,Everyth. Ev.wh./T-Mobile
+234,564,31,799,gb,United Kingdom,44,Everyth. Ev.wh./T-Mobile
+234,564,32,815,gb,United Kingdom,44,Everyth. Ev.wh./T-Mobile
+234,564,27,639,gb,United Kingdom,44,Vodafone
+234,564,09,159,gb,United Kingdom,44,Tismi
+234,564,25,607,gb,United Kingdom,44,Truphone
+234,564,51,1311,gb,United Kingdom,44,Jersey Telecom
+234,564,23,575,gb,United Kingdom,44,Vectofone Mobile Wifi
+234,564,91,2335,gb,United Kingdom,44,Vodafone
+234,564,15,351,gb,United Kingdom,44,Vodafone
+234,564,78,1935,gb,United Kingdom,44,Wave Telecom Ltd
+310,784,050,80,us,United States,1,
+310,784,880,2176,us,United States,1,
+310,784,850,2128,us,United States,1,Aeris Comm. Inc.
+310,784,640,1600,us,United States,1,
+310,784,510,1296,us,United States,1,Airtel Wireless LLC
+310,784,190,400,us,United States,1,Unknown
+312,786,090,144,us,United States,1,Allied Wireless Communications Corporation
+311,785,130,304,us,United States,1,
+310,784,710,1808,us,United States,1,Arctic Slope Telephone Association Cooperative Inc.
+310,784,680,1664,us,United States,1,AT&T Wireless Inc.
+310,784,070,112,us,United States,1,AT&T Wireless Inc.
+310,784,560,1376,us,United States,1,AT&T Wireless Inc.
+310,784,410,1040,us,United States,1,AT&T Wireless Inc.
+310,784,380,896,us,United States,1,AT&T Wireless Inc.
+310,784,170,368,us,United States,1,AT&T Wireless Inc.
+310,784,150,336,us,United States,1,AT&T Wireless Inc.
+310,784,980,2432,us,United States,1,AT&T Wireless Inc.
+311,785,810,2064,us,United States,1,Bluegrass Wireless LLC
+311,785,800,2048,us,United States,1,Bluegrass Wireless LLC
+311,785,440,1088,us,United States,1,Bluegrass Wireless LLC
+310,784,900,2304,us,United States,1,Cable & Communications Corp.
+311,785,590,1424,us,United States,1,California RSA No. 3 Limited Partnership
+311,785,500,1280,us,United States,1,Cambridge Telephone Company Inc.
+310,784,830,2096,us,United States,1,Caprock Cellular Ltd.
+310,784,013,19,us,United States,1,Verizon Wireless
+311,785,281,641,us,United States,1,Verizon Wireless
+311,785,486,1158,us,United States,1,Verizon Wireless
+311,785,270,624,us,United States,1,Verizon Wireless
+311,785,286,646,us,United States,1,Verizon Wireless
+311,785,275,629,us,United States,1,Verizon Wireless
+311,785,480,1152,us,United States,1,Verizon Wireless
+310,784,012,18,us,United States,1,Verizon Wireless
+311,785,280,640,us,United States,1,Verizon Wireless
+311,785,485,1157,us,United States,1,Verizon Wireless
+311,785,110,272,us,United States,1,Verizon Wireless
+311,785,285,645,us,United States,1,Verizon Wireless
+311,785,274,628,us,United States,1,Verizon Wireless
+311,785,390,912,us,United States,1,Verizon Wireless
+310,784,010,16,us,United States,1,Verizon Wireless
+311,785,279,633,us,United States,1,Verizon Wireless
+311,785,484,1156,us,United States,1,Verizon Wireless
+310,784,910,2320,us,United States,1,Verizon Wireless
+311,785,284,644,us,United States,1,Verizon Wireless
+311,785,489,1161,us,United States,1,Verizon Wireless
+311,785,273,627,us,United States,1,Verizon Wireless
+311,785,289,649,us,United States,1,Verizon Wireless
+310,784,004,4,us,United States,1,Verizon Wireless
+311,785,278,632,us,United States,1,Verizon Wireless
+311,785,483,1155,us,United States,1,Verizon Wireless
+310,784,890,2192,us,United States,1,Verizon Wireless
+311,785,283,643,us,United States,1,Verizon Wireless
+311,785,488,1160,us,United States,1,Verizon Wireless
+311,785,272,626,us,United States,1,Verizon Wireless
+311,785,288,648,us,United States,1,Verizon Wireless
+311,785,277,631,us,United States,1,Verizon Wireless
+311,785,482,1154,us,United States,1,Verizon Wireless
+310,784,590,1424,us,United States,1,Verizon Wireless
+311,785,282,642,us,United States,1,Verizon Wireless
+311,785,487,1159,us,United States,1,Verizon Wireless
+311,785,271,625,us,United States,1,Verizon Wireless
+311,785,287,647,us,United States,1,Verizon Wireless
+311,785,276,630,us,United States,1,Verizon Wireless
+311,785,481,1153,us,United States,1,Verizon Wireless
+312,786,270,624,us,United States,1,Cellular Network Partnership LLC
+310,784,360,864,us,United States,1,Cellular Network Partnership LLC
+312,786,280,640,us,United States,1,Cellular Network Partnership LLC
+311,785,190,400,us,United States,1,
+310,784,030,48,us,United States,1,
+310,784,480,1152,us,United States,1,Choice Phone LLC
+311,785,120,288,us,United States,1,Choice Phone LLC
+310,784,630,1584,us,United States,1,
+310,784,420,1056,us,United States,1,Cincinnati Bell Wireless LLC
+310,784,180,384,us,United States,1,Cingular Wireless
+310,784,620,1568,us,United States,1,Coleman County Telco /Trans TX
+311,785,040,64,us,United States,1,
+310,784,06,111,us,United States,1,Consolidated Telcom
+310,784,60,1551,us,United States,1,Consolidated Telcom
+310,784,26,623,us,United States,1,
+312,786,380,896,us,United States,1,
+310,784,930,2352,us,United States,1,
+311,785,240,576,us,United States,1,
+310,784,080,128,us,United States,1,
+310,784,700,1792,us,United States,1,Cross Valliant Cellular Partnership
+312,786,030,48,us,United States,1,Cross Wireless Telephone Co.
+311,785,140,320,us,United States,1,Cross Wireless Telephone Co.
+311,785,520,1312,us,United States,1,
+312,786,040,64,us,United States,1,Custer Telephone Cooperative Inc.
+310,784,440,1088,us,United States,1,Dobson Cellular Systems
+310,784,990,2448,us,United States,1,E.N.M.R. Telephone Coop.
+310,784,750,1872,us,United States,1,East Kentucky Network LLC
+312,786,130,304,us,United States,1,East Kentucky Network LLC
+312,786,120,288,us,United States,1,East Kentucky Network LLC
+310,784,090,144,us,United States,1,Edge Wireless LLC
+310,784,610,1552,us,United States,1,Elkhart TelCo. / Epic Touch Co.
+311,785,210,528,us,United States,1,
+311,785,311,785,us,United States,1,Farmers
+311,785,460,1120,us,United States,1,Fisher Wireless Services Inc.
+310,784,430,1072,us,United States,1,GCI Communication Corp.
+311,785,370,880,us,United States,1,GCI Communication Corp.
+310,784,920,2336,us,United States,1,Get Mobile Inc.
+310,784,970,2416,us,United States,1,
+311,785,340,832,us,United States,1,Illinois Valley Cellular RSA 2 Partnership
+311,785,030,48,us,United States,1,
+311,785,410,1040,us,United States,1,Iowa RSA No. 2 Limited Partnership
+312,786,170,368,us,United States,1,Iowa RSA No. 2 Limited Partnership
+310,784,770,1904,us,United States,1,Iowa Wireless Services LLC
+310,784,650,1616,us,United States,1,Jasper
+310,784,870,2160,us,United States,1,Kaplan Telephone Company Inc.
+312,786,180,384,us,United States,1,Keystone Wireless LLC
+310,784,690,1680,us,United States,1,Keystone Wireless LLC
+311,785,310,784,us,United States,1,Lamar County Cellular
+310,784,016,22,us,United States,1,Leap Wireless International Inc.
+311,785,090,144,us,United States,1,
+310,784,040,64,us,United States,1,Matanuska Tel. Assn. Inc.
+310,784,780,1920,us,United States,1,Message Express Co. / Airlink PCS
+311,785,660,1632,us,United States,1,
+311,785,330,816,us,United States,1,Michigan Wireless LLC
+311,785,000,0,us,United States,1,
+310,784,400,1024,us,United States,1,Minnesota South. Wirel. Co. / Hickory
+312,786,010,16,us,United States,1,Missouri RSA No 5 Partnership
+311,785,920,2336,us,United States,1,Missouri RSA No 5 Partnership
+311,785,020,32,us,United States,1,Missouri RSA No 5 Partnership
+311,785,010,16,us,United States,1,Missouri RSA No 5 Partnership
+312,786,220,544,us,United States,1,Missouri RSA No 5 Partnership
+310,784,350,848,us,United States,1,Mohave Cellular LP
+310,784,570,1392,us,United States,1,MTPCS LLC
+310,784,290,656,us,United States,1,NEP Cellcorp Inc.
+310,784,34,847,us,United States,1,Nevada Wireless LLC
+311,785,380,896,us,United States,1,
+310,784,600,1536,us,United States,1,New-Cell Inc.
+311,785,100,256,us,United States,1,
+311,785,300,768,us,United States,1,Nexus Communications Inc.
+310,784,130,304,us,United States,1,North Carolina RSA 3 Cellular Tel. Co.
+311,785,610,1552,us,United States,1,North Dakota Network Company
+312,786,230,560,us,United States,1,North Dakota Network Company
+310,784,450,1104,us,United States,1,Northeast Colorado Cellular Inc.
+311,785,710,1808,us,United States,1,Northeast Wireless Networks LLC
+310,784,670,1648,us,United States,1,Northstar
+310,784,011,17,us,United States,1,Northstar
+311,785,420,1056,us,United States,1,Northwest Missouri Cellular Limited Partnership
+310,784,540,1344,us,United States,1,
+310,784,760,1888,us,United States,1,Panhandle Telephone Cooperative Inc.
+310,784,580,1408,us,United States,1,PCS ONE
+311,785,170,368,us,United States,1,PetroCom
+311,785,670,1648,us,United States,1,Pine Belt Cellular Inc.
+311,785,080,128,us,United States,1,
+310,784,790,1936,us,United States,1,
+310,784,100,256,us,United States,1,Plateau Telecommunications Inc.
+310,784,940,2368,us,United States,1,Poka Lambro Telco Ltd.
+311,785,540,1344,us,United States,1,
+311,785,730,1840,us,United States,1,
+310,784,500,1280,us,United States,1,Public Service Cellular Inc.
+312,786,160,352,us,United States,1,RSA 1 Limited Partnership
+311,785,430,1072,us,United States,1,RSA 1 Limited Partnership
+311,785,350,848,us,United States,1,Sagebrush Cellular Inc.
+311,785,910,2320,us,United States,1,
+310,784,46,1135,us,United States,1,SIMMETRY
+311,785,260,608,us,United States,1,SLO Cellular Inc / Cellular One of San Luis
+310,784,320,800,us,United States,1,Smith Bagley Inc.
+310,784,15,351,us,United States,1,Unknown
+316,790,011,17,us,United States,1,Southern Communications Services Inc.
+312,786,530,1328,us,United States,1,Sprint Spectrum
+311,785,490,1168,us,United States,1,Sprint Spectrum
+310,784,120,288,us,United States,1,Sprint Spectrum
+316,790,010,16,us,United States,1,Sprint Spectrum
+312,786,190,400,us,United States,1,Sprint Spectrum
+311,785,880,2176,us,United States,1,Sprint Spectrum
+311,785,870,2160,us,United States,1,Sprint Spectrum
+310,784,200,512,us,United States,1,T-Mobile
+310,784,250,592,us,United States,1,T-Mobile
+310,784,160,352,us,United States,1,T-Mobile
+310,784,240,576,us,United States,1,T-Mobile
+310,784,660,1632,us,United States,1,T-Mobile
+310,784,230,560,us,United States,1,T-Mobile
+310,784,31,799,us,United States,1,T-Mobile
+310,784,220,544,us,United States,1,T-Mobile
+310,784,270,624,us,United States,1,T-Mobile
+310,784,210,528,us,United States,1,T-Mobile
+310,784,260,608,us,United States,1,T-Mobile
+310,784,330,816,us,United States,1,T-Mobile
+310,784,800,2048,us,United States,1,T-Mobile
+310,784,300,768,us,United States,1,T-Mobile
+310,784,280,640,us,United States,1,T-Mobile
+310,784,310,784,us,United States,1,T-Mobile
+311,785,740,1856,us,United States,1,
+310,784,740,1856,us,United States,1,Telemetrix Inc.
+310,784,14,335,us,United States,1,Testing
+310,784,950,2384,us,United States,1,Unknown
+310,784,860,2144,us,United States,1,Texas RSA 15B2 Limited Partnership
+311,785,830,2096,us,United States,1,Thumb Cellular Limited Partnership
+311,785,050,80,us,United States,1,Thumb Cellular Limited Partnership
+310,784,460,1120,us,United States,1,TMP Corporation
+310,784,490,1168,us,United States,1,Triton PCS
+312,786,290,656,us,United States,1,Uintah Basin Electronics Telecommunications Inc.
+311,785,860,2144,us,United States,1,Uintah Basin Electronics Telecommunications Inc.
+310,784,960,2400,us,United States,1,Uintah Basin Electronics Telecommunications Inc.
+310,784,020,32,us,United States,1,Union Telephone Co.
+311,785,220,544,us,United States,1,United States Cellular Corp.
+310,784,730,1840,us,United States,1,United States Cellular Corp.
+311,785,650,1616,us,United States,1,United Wireless Communications Inc.
+310,784,38,911,us,United States,1,USA 3650 AT&T
+310,784,520,1312,us,United States,1,VeriSign
+310,784,003,3,us,United States,1,Unknown
+310,784,23,575,us,United States,1,Unknown
+310,784,24,591,us,United States,1,Unknown
+310,784,25,607,us,United States,1,Unknown
+310,784,530,1328,us,United States,1,West Virginia Wireless
+310,784,26,623,us,United States,1,Unknown
+310,784,340,832,us,United States,1,Westlink Communications LLC
+311,785,150,336,us,United States,1,
+311,785,070,112,us,United States,1,Wisconsin RSA #7 Limited Partnership
+310,784,390,912,us,United States,1,Yorkville Telephone Cooperative
+748,1864,01,31,uy,Uruguay,598,Ancel/Antel
+748,1864,03,63,uy,Uruguay,598,Ancel/Antel
+748,1864,10,271,uy,Uruguay,598,Claro/AM Wireless
+748,1864,07,127,uy,Uruguay,598,MOVISTAR
+434,1076,04,79,uz,Uzbekistan,998,Bee Line/Unitel
+434,1076,01,31,uz,Uzbekistan,998,Buztel
+434,1076,07,127,uz,Uzbekistan,998,MTS/Uzdunrobita
+434,1076,05,95,uz,Uzbekistan,998,Ucell/Coscom
+434,1076,02,47,uz,Uzbekistan,998,Uzmacom
+541,1345,05,95,vu,Vanuatu,678,DigiCel
+541,1345,01,31,vu,Vanuatu,678,SMILE
+734,1844,03,63,ve,Venezuela,58,DigiTel C.A.
+734,1844,02,47,ve,Venezuela,58,DigiTel C.A.
+734,1844,01,31,ve,Venezuela,58,DigiTel C.A.
+734,1844,06,111,ve,Venezuela,58,Movilnet C.A.
+734,1844,04,79,ve,Venezuela,58,Movistar/TelCel
+452,1106,07,127,vn,Viet Nam,84,Beeline
+452,1106,01,31,vn,Viet Nam,84,Mobifone
+452,1106,03,63,vn,Viet Nam,84,S-Fone/Telecom
+452,1106,05,95,vn,Viet Nam,84,VietnaMobile
+452,1106,08,143,vn,Viet Nam,84,Viettel Mobile
+452,1106,06,111,vn,Viet Nam,84,Viettel Mobile
+452,1106,04,79,vn,Viet Nam,84,Viettel Mobile
+452,1106,02,47,vn,Viet Nam,84,Vinaphone
+376,886,50,1295,vi,Virgin Islands U.S.,1340,Digicel
+421,1057,04,79,ye,Yemen,967,HITS/Y Unitel
+421,1057,02,47,ye,Yemen,967,MTN/Spacetel
+421,1057,01,31,ye,Yemen,967,Sabaphone
+421,1057,03,63,ye,Yemen,967,Yemen Mob. CDMA
+645,1605,03,63,zm,Zambia,260,Zamtel/Cell Z/MTS
+645,1605,02,47,zm,Zambia,260,MTN/Telecel
+645,1605,01,31,zm,Zambia,260,Airtel/Zain/Celtel
+648,1608,04,79,zw,Zimbabwe,263,Econet
+648,1608,01,31,zw,Zimbabwe,263,Net One
+648,1608,03,63,zw,Zimbabwe,263,Telecel
diff --git a/config/client_config/onoff_huawei_hilink.sh b/config/client_config/onoff_huawei_hilink.sh
new file mode 100644
index 00000000..9b3f8413
--- /dev/null
+++ b/config/client_config/onoff_huawei_hilink.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# connect/disconnect Huawei mobile data stick in Hilink mode (e.g. E3372h)
+# ========================================================================
+#
+# options: -u, --user - user name (default "admin")
+# -P, --password - password
+# -h, --host - host ip address (default 192.168.8.1)
+# -d, --devname - device name (IP is extracted using default route)
+# -p, --pin - PIN of SIM card
+# -c, --connect - connect 0/1 to set datamode off/on
+#
+# required software: curl, base64, sha256sum
+#
+# zbchristian 2021
+
+# include the hilink API (defaults: hilink_user=admin, hilink_host=192.168.8.1)
+source /usr/local/sbin/huawei_hilink_api.sh
+
+# include the raspap helper functions
+source /usr/local/sbin/raspap_helpers.sh
+
+datamode=""
+devname=""
+while [ -n "$1" ]; do
+ case "$1" in
+ -u|--user) hilink_user="$2"; shift ;;
+ -P|--password) hilink_password="$2"; shift ;;
+ -p|--pin) if [[ $2 =~ ^[0-9]{4,8} ]]; then hilink_pin="$2"; fi; shift ;;
+ -h|--host) hilink_host="$2"; shift ;;
+ -d|--devname) devname="$2"; shift ;;
+ -c|--connect) if [ "$2" = "1" ]; then datamode=1; else datamode=0; fi; shift ;;
+ esac
+ shift
+done
+
+if [ ! -z "$devname" ]; then # get host IP for given device name
+ gw=$(ip route list | sed -rn "s/default via (([0-9]{1,3}\.){3}[0-9]{1,3}).*dev $devname.*/\1/p")
+ if [ -z "$gw" ]; then exit; fi # device name not found in routing list -> abort
+ hilink_host="$gw"
+fi
+
+if [ -z "$hilink_password" ] || [ -z "$hilink_pin" ]; then
+ _getAuthRouter
+ if [ ! -z "$raspap_user" ]; then hilink_user="$raspap_user"; fi
+ if [ ! -z "$raspap_password" ]; then hilink_password="$raspap_password"; fi
+ if [ ! -z "$raspap_pin" ]; then hilink_pin="$raspap_pin"; fi
+fi
+
+echo "Hilink: switch device at $hilink_host to mode $datamode" | systemd-cat
+
+status="usage: -c 1/0 to disconnect/connect"
+if [ -z "$datamode" ] || [ ! _initHilinkAPI ]; then echo "Hilink: failed - return status: $status"; exit; fi
+
+if ! _switchMobileData "$datamode"; then echo -n "Hilink: could not switch the data mode on/off . Error: ";_getErrorText; fi
+
+if ! _closeHilinkAPI; then echo -n "Hilink: failed - return status: $status . Error: ";_getErrorText; fi
+
+
diff --git a/config/client_config/ppp0_route.sh b/config/client_config/ppp0_route.sh
new file mode 100644
index 00000000..f1d6bc3d
--- /dev/null
+++ b/config/client_config/ppp0_route.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# get gateway and ip address of UTMS modem connected to ppp0
+# add a default route
+# called by /etc/network/interfaces.d/ppp0, when device is coming up
+#
+ppp0rt=""
+let i=1
+while [ -z "$ppp0rt" ] ; do
+ let i+=1
+ if [ $i -gt 20 ]; then
+ exit 1
+ fi
+ sleep 1
+ ppp0rt=`ip route list | grep -m 1 ppp0`
+done
+gate=`echo $ppp0rt | sed -rn 's/(([0-9]{1,3}\.){3}[0-9]{1,3}).*ppp0.*src (([0-9]{1,3}\.){3}[0-9]{1,3})/\1/p'`
+src=`echo $ppp0rt | sed -rn 's/(([0-9]{1,3}\.){3}[0-9]{1,3}).*ppp0.*src (([0-9]{1,3}\.){3}[0-9]{1,3})/\3/p'`
+
+ip route add default via $gate proto dhcp src $src metric 10
+exit 0
diff --git a/config/client_config/ppp0_setpin.sh b/config/client_config/ppp0_setpin.sh
new file mode 100644
index 00000000..74c1b415
--- /dev/null
+++ b/config/client_config/ppp0_setpin.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# in case /dev/ttyUSB0 does not exist, wait for it at most 30 seconds
+let i=1
+while ! test -c /dev/ttyUSB0; do
+ let i+=1
+ if [ $i -gt 2 ]; then
+ logger -s -t setpin "/dev/ttyUSB0 does not exist"
+ exit 3
+ fi
+ logger -s -t setpin "waiting 3 seconds for /dev/ttyUSB0"
+ sleep 3
+done
+# check for pin and set it if necessary
+wvdial pinstatus 2>&1 | grep -q '^+CPIN: READY'
+if [ $? -eq 0 ]; then
+ logger -s -t setpin "SIM card is ready to use :-)"
+else
+ logger -s -t setpin "setting PIN"
+ wvdial pin 2>/dev/null
+fi
+exit 0
diff --git a/config/client_config/raspap_helpers.sh b/config/client_config/raspap_helpers.sh
new file mode 100644
index 00000000..a20ba309
--- /dev/null
+++ b/config/client_config/raspap_helpers.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Helper functions to extract informations from RaspAP config/settings
+#
+# zbchristian 2021
+#
+# get the values of a RaspAP config variable
+# call: _getRaspapConfig RASPAP_MOBILEDATA_CONFIG
+
+raspap_webroot="/var/www/html"
+
+function _getWebRoot() {
+ local path
+ path=$(cat /etc/lighttpd/lighttpd.conf | sed -rn "s/server.document-root \s*= \"([^ \s]*)\"/\1/p")
+ if [ ! -z "$path" ]; then raspap_webroot="$path"; fi
+ if [ -z "$path" ]; then return 1; else return 0; fi
+}
+
+# expand an RaspAP config variable utilizing PHP
+function _getRaspapConfig() {
+ local conf var
+ raspap_config=""
+ var="$1"
+ if [ ! -z "$var" ]; then
+ if ! _getWebRoot; then return 1; fi
+ conf="$raspap_webroot/includes/config.php"
+ if [ -f "$conf" ]; then
+ conf=$(php -r 'include "'$conf'"; echo '$var';' 2> /dev/null)
+ if [ ! -z "$conf" ] && [ -d ${conf%/*} ]; then raspap_config="$conf"; fi
+ fi
+ fi
+ if [ -z "$raspap_config" ]; then return 1; else return 0; fi
+}
+
+# Username and password for mobile data devices is stored in a file (RASPAP_MOBILEDATA_CONFIG)
+function _getAuthRouter() {
+ local mfile mdata pin user pw
+ if ! _getRaspapConfig "RASPI_MOBILEDATA_CONFIG"; then return 1; fi
+ mfile="$raspap_config"
+ if [ -f $mfile ]; then
+ mdata=$(cat "$mfile")
+ pin=$(echo "$mdata" | sed -rn 's/pin = ([^ \s]*)/\1/ip')
+ if [ ! -z "$pin" ]; then raspap_pin="$pin"; fi
+ user=$(echo "$mdata" | sed -rn 's/router_user = ([^ \s]*)/\1/ip')
+ if [ ! -z "$user" ]; then raspap_user="$user"; fi
+ pw=$(echo "$mdata" | sed -rn 's/router_pw = ([^ \s]*)/\1/ip')
+ if [ ! -z "$pw" ]; then raspap_password="$pw"; fi
+ return 0
+ fi
+ return 1
+}
diff --git a/config/client_config/start_huawei_hilink@.service b/config/client_config/start_huawei_hilink@.service
new file mode 100644
index 00000000..b735b1b1
--- /dev/null
+++ b/config/client_config/start_huawei_hilink@.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Bring up HUAWEI mobile hilink device
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=/bin/sleep 15
+ExecStart=/usr/local/sbin/onoff_huawei_hilink.sh -c 1 -d %i
+
+[Install]
+Alias=start_ltemodem.service
+WantedBy=multi-user.target
+
diff --git a/config/client_config/start_ppp0_device.service b/config/client_config/start_ppp0_device.service
new file mode 100644
index 00000000..2fc88e5c
--- /dev/null
+++ b/config/client_config/start_ppp0_device.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Start ppp0 interface
+BindsTo=dev-ttyUSB0.device
+After=dev-ttyUSB0.device
+
+[Service]
+Type=forking
+RemainAfterExit=yes
+ExecStart=/sbin/ifup ppp0
+ExecStop=/sbin/ifdown ppp0
+
+[Install]
+Alias=startppp0.service
+WantedBy=multi-user.target
+
+
diff --git a/config/client_config/wvdial.conf b/config/client_config/wvdial.conf
new file mode 100644
index 00000000..8de5b3c6
--- /dev/null
+++ b/config/client_config/wvdial.conf
@@ -0,0 +1,21 @@
+[Dialer Defaults]
+Modem Type = Analog Modem
+ISDN = 0
+Baud = 9600
+Modem = /dev/ttyUSB0
+
+[Dialer pin]
+Init1 = AT+CPIN="XXXX"
+
+[Dialer connect]
+Init1 = ATZ
+Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
+Init3 = AT+CGDCONT=1,"IP","web.vodafone.de"
+New PPPD = yes
+Phone = *99#
+Password = me
+Username = vodafone
+Stupid Mode = 1
+
+[Dialer pinstatus]
+Init1 = AT+CPIN?
diff --git a/config/client_udev_prototypes.json b/config/client_udev_prototypes.json
new file mode 100644
index 00000000..bd49f419
--- /dev/null
+++ b/config/client_udev_prototypes.json
@@ -0,0 +1,64 @@
+{
+ "info": "UDEV rules for different client types. $...$ expressions will be replaces automatically ($MAC$, $IDVENDOR$, $IDPRODUCT$, $DEVNAME$)",
+ "udev_rules_file": "/etc/udev/rules.d/80-raspap-net-devices.rules",
+ "script_path": "/usr/local/sbin",
+ "network_devices": [
+ {
+ "type": "eth",
+ "type_info": "ethernet port",
+ "clientid": 0,
+ "comment": "standard ethernet port",
+ "name_prefix": "eth",
+ "udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", ATTR{address}==\"$MAC$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"eth\" "
+ },
+ {
+ "type": "usb",
+ "type_info": "usb network interface",
+ "clientid": 1,
+ "comment": "network interface - e.g. USB tethering of an Android phone ",
+ "name_prefix": "usb",
+ "udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"$IDVENDOR$\", ATTRS{idProduct}==\"$IDPRODUCT$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"eth\" "
+ },
+ {
+ "type": "wlan",
+ "type_info": "wireless adapter",
+ "clientid": 2,
+ "comment": "standard wireless interface",
+ "name_prefix": "wlan",
+ "udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", ATTR{address}==\"$MAC$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"wlan\" "
+ },
+ {
+ "type": "ppp",
+ "type_info": "mobile data modem",
+ "clientid": 3,
+ "name_prefix": "ppp",
+ "comment": "recognized mobile data modems are automatically named as ppp0-9. Renaming is not possible. Dialin service relies on the name",
+ "udev_rule": "SUBSYSTEM==\"tty\", KERNEL==\"ttyUSB0\", TAG+=\"systemd\", ENV{SYSTEMD_WANTS}=\"start start_ppp0_device.service\" "
+ },
+ {
+ "type": "hilink",
+ "type_info": "Huawei Hilink",
+ "clientid": 4,
+ "comment": "Huawei mobile data device in router mode. Control via HTTP. Device is connecting via service",
+ "name_prefix": "hilink",
+ "default_ip": "192.168.8.1",
+ "udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"$IDVENDOR$\", ATTRS{idProduct}==\"$IDPRODUCT$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"hilink\", TAG+=\"systemd\", ENV{SYSTEMD_WANTS}=\"start start_huawei_hilink@hilink%n.service\" "
+ },
+ {
+ "type": "phone",
+ "type_info": "USB tethered phone",
+ "clientid": 5,
+ "comment": "ethernet access provided by tethering from phone via USB",
+ "name_prefix": "phone",
+ "udev_rule": "SUBSYSTEM==\"net\", ACTION==\"add\", SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"$IDVENDOR$\", ATTRS{idProduct}==\"$IDPRODUCT$\", NAME=\"$DEVNAME$\", ENV{raspapType}=\"phone\" "
+ },
+ {
+ "type": "tun",
+ "type_info": "tunnel device",
+ "clientid": -1,
+ "comment": "tunneling device used by OpenVPN",
+ "name_prefix": "tun"
+ }
+ ]
+}
+
diff --git a/config/config.php b/config/config.php
index 046e36a2..4589092d 100755
--- a/config/config.php
+++ b/config/config.php
@@ -1,33 +1,49 @@
-
-**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
-
-- [Installation](#installation)
- - [CDN](#cdn)
- - [Download](#download)
- - [NPM](#npm)
- - [Yarn](#yarn)
-- [Usage](#usage)
- - [Initialize With HTML](#initialize-with-html)
- - [Initialize With Code](#initialize-with-code)
-- [API](#api)
- - [Options](#options)
- - [Methods](#methods)
-- [Events](#events)
- - [Event Propagation](#event-propagation)
- - [API vs Input](#api-vs-input)
-
-
-
-**************************************************************************************************
-
-# Installation
-
-## CDN
-```html
-
-
-```
-
-## Download
-[Latest GitHub Release](https://github.com/gitbrent/bootstrap4-toggle/releases/latest)
-
-## NPM
-```ksh
-npm install bootstrap4-toggle
-```
-
-## Yarn
-```ksh
-yarn add bootstrap4-toggle
-```
-
-# Usage
-
-## Initialize With HTML
-Simply add `data-toggle="toggle"` to automatically convert a plain checkbox into a bootstrap 4 toggle.
-
-```html
-
-```
-
-## Initialize With Code
-Toggles can also be initialized via JavaScript code.
-
-EX: Initialize id `chkToggle` with a single line of JavaScript.
-```html
-
-
-```
-
-# API
-
-## Options
-* Options can be passed via data attributes or JavaScript
-* For data attributes, append the option name to `data-` (ex: `data-on="Enabled"`)
-
-```html
-
-
-
-```
-
-Name |Type |Default |Description |
-----------|-----------|----------|----------------------------|
-`on` |string/html|"On" |Text of the on toggle
-`off` |string/html|"Off" |Text of the off toggle
-`size` |string |"normal" |Size of the toggle. Possible values are: `large`, `normal`, `small`, `mini`.
-`onstyle` |string |"primary" |Style of the on toggle. Possible values are: `primary`,`secondary`,`success`,`danger`,`warning`,`info`,`light`,`dark`
-`offstyle`|string |"light" |Style of the off toggle. Possible values are: `primary`,`secondary`,`success`,`danger`,`warning`,`info`,`light`,`dark`
-`style` |string | |Appends the value to the class attribute of the toggle. This can be used to apply custom styles. Refer to Custom Styles for reference.
-`width` |integer |*null* |Sets the width of the toggle. if set to *null*, width will be auto-calculated.
-`height` |integer |*null* |Sets the height of the toggle. if set to *null*, height will be auto-calculated.
-
-## Methods
-Methods can be used to control toggles directly.
-
-```html
-
-```
-
-Method |Example |Description
------------|------------------------------------------------|------------------------------------------
-initialize | `$('#toggle-demo').bootstrapToggle()` |Initializes the toggle plugin with options
-destroy | `$('#toggle-demo').bootstrapToggle('destroy')` |Destroys the toggle
-on | `$('#toggle-demo').bootstrapToggle('on')` |Sets the toggle to 'On' state
-off | `$('#toggle-demo').bootstrapToggle('off')` |Sets the toggle to 'Off' state
-toggle | `$('#toggle-demo').bootstrapToggle('toggle')` |Toggles the state of the toggle on/off
-enable | `$('#toggle-demo').bootstrapToggle('enable')` |Enables the toggle
-disable | `$('#toggle-demo').bootstrapToggle('disable')` |Disables the toggle
-
-# Events
-
-## Event Propagation
-Note All events are propagated to and from input element to the toggle.
-
-You should listen to events from the `` directly rather than look for custom events.
-
-```html
-
-
-
-```
-
-## API vs Input
-This also means that using the API or Input to trigger events will work both ways.
-
-```html
-
-
-
-
-
-
-```
diff --git a/dist/bootstrap4-toggle/css/bootstrap4-toggle.min.css b/dist/bootstrap4-toggle/css/bootstrap4-toggle.min.css
deleted file mode 100644
index dc10fe12..00000000
--- a/dist/bootstrap4-toggle/css/bootstrap4-toggle.min.css
+++ /dev/null
@@ -1,42 +0,0 @@
-/*\
-|*| ========================================================================
-|*| Bootstrap Toggle: bootstrap4-toggle.css v3.5.0
-|*| https://gitbrent.github.io/bootstrap-toggle/
-|*| ========================================================================
-|*| Copyright 2018-2019 Brent Ely
-|*| Licensed under MIT
-|*| ========================================================================
-\*/
-.btn-group-xs>.btn,.btn-xs{padding:.35rem .4rem .25rem;font-size:.875rem;line-height:.5;border-radius:.2rem}
-.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-1.25rem;margin-right:.35rem}
-.toggle{position:relative;overflow:hidden}
-.toggle.btn.btn-light,.toggle.btn.btn-outline-light{border-color:rgba(0,0,0,.15)}
-.toggle input[type=checkbox]{display:none}
-.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none}
-.toggle-group label,.toggle-group span{cursor:pointer}
-.toggle.off .toggle-group{left:-100%}
-.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0}
-.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0;box-shadow:none}
-.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px;background-color:#fff}
-.toggle.btn-outline-primary .toggle-handle{background-color:var(--primary);border-color:var(--primary)}
-.toggle.btn-outline-secondary .toggle-handle{background-color:var(--secondary);border-color:var(--secondary)}
-.toggle.btn-outline-success .toggle-handle{background-color:var(--success);border-color:var(--success)}
-.toggle.btn-outline-danger .toggle-handle{background-color:var(--danger);border-color:var(--danger)}
-.toggle.btn-outline-warning .toggle-handle{background-color:var(--warning);border-color:var(--warning)}
-.toggle.btn-outline-info .toggle-handle{background-color:var(--info);border-color:var(--info)}
-.toggle.btn-outline-light .toggle-handle{background-color:var(--light);border-color:var(--light)}
-.toggle.btn-outline-dark .toggle-handle{background-color:var(--dark);border-color:var(--dark)}
-.toggle[class*=btn-outline]:hover .toggle-handle{background-color:var(--light);opacity:.5}
-.toggle.btn{min-width:3.7rem;min-height:2.15rem}
-.toggle-on.btn{padding-right:1.5rem}
-.toggle-off.btn{padding-left:1.5rem}
-.toggle.btn-lg{min-width:5rem;min-height:2.815rem}
-.toggle-on.btn-lg{padding-right:2rem}
-.toggle-off.btn-lg{padding-left:2rem}
-.toggle-handle.btn-lg{width:2.5rem}
-.toggle.btn-sm{min-width:3.125rem;min-height:1.938rem}
-.toggle-on.btn-sm{padding-right:1rem}
-.toggle-off.btn-sm{padding-left:1rem}
-.toggle.btn-xs{min-width:2.19rem;min-height:1.375rem}
-.toggle-on.btn-xs{padding-right:.8rem}
-.toggle-off.btn-xs{padding-left:.8rem}
diff --git a/dist/bootstrap4-toggle/js/bootstrap4-toggle.min.js b/dist/bootstrap4-toggle/js/bootstrap4-toggle.min.js
deleted file mode 100644
index 7c982e9a..00000000
--- a/dist/bootstrap4-toggle/js/bootstrap4-toggle.min.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*\
-|*| ========================================================================
-|*| Bootstrap Toggle: bootstrap4-toggle.js v3.5.0
-|*| https://gitbrent.github.io/bootstrap-toggle/
-|*| ========================================================================
-|*| Copyright 2018-2019 Brent Ely
-|*| Licensed under MIT
-|*| ========================================================================
-\*/
-+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="3.5.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"light",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size||"lg"===this.options.size?"btn-lg":"small"===this.options.size||"sm"===this.options.size?"btn-sm":"mini"===this.options.size||"xs"===this.options.size?"btn-xs":"",c=a('