├── .gitignore
├── LICENSE
├── README.md
├── pirania-app
└── files
│ ├── etc
│ └── pirania
│ │ └── content.json
│ ├── usr
│ ├── libexec
│ │ └── rpcd
│ │ │ └── pirania-app
│ └── share
│ │ └── rpcd
│ │ └── acl.d
│ │ └── pirania-app.json
│ └── www
│ ├── cgi-bin
│ ├── error_handler
│ └── pirania
│ │ ├── client_ip
│ │ ├── handle_auth
│ │ ├── handle_voucher
│ │ └── portal
│ └── portal
│ ├── admin.html
│ ├── auth.html
│ ├── authenticated.html
│ ├── css
│ ├── loader2.css
│ ├── main.css
│ ├── mobile-icons.css
│ └── normalize.css
│ ├── fail.html
│ ├── index.html
│ ├── info.html
│ └── js
│ ├── admin.js
│ ├── content.js
│ ├── int.js
│ ├── tabs.js
│ ├── ubusFetch.js
│ └── voucher.js
└── pirania
└── files
├── etc
├── config
│ └── pirania
├── init.d
│ ├── pirania
│ ├── pirania-dnsmasq
│ └── pirania-uhttpd
└── pirania
│ └── db.csv
├── usr
├── bin
│ ├── captive-portal
│ └── voucher
├── lib
│ └── lua
│ │ └── voucher
│ │ ├── config.lua
│ │ ├── db.lua
│ │ ├── debugtools.lua
│ │ ├── functools.lua
│ │ ├── hooks.lua
│ │ ├── logic.lua
│ │ └── utils.lua
├── libexec
│ └── rpcd
│ │ └── pirania
└── share
│ └── rpcd
│ └── acl.d
│ └── pirania.json
└── www
└── pirania-redirect
└── redirect
/.gitignore:
--------------------------------------------------------------------------------
1 | .history
2 | .DS_Store
3 | build-router.sh
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 LibreMesh.org
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated! Moved to https://github.com/libremesh/lime-packages/tree/master/packages
2 |
3 | 
4 |
5 | ## Voucher and Captive Portal solution for community networks
6 |
7 | *ALPHA Software, don't use in production*
8 |
9 |
10 | This tool allows an administrator to manage a voucher system to get through the gateway.
11 |
12 | It could be used in a community that wants to share an Internet connection and for that the user's pay a fraction each, but needs the payment from everyone. So the vouchers allows to control the payments via the control of the access to Internet.
13 |
14 | ## Features
15 |
16 | This are the currently implemented features:
17 | * Runs directly from the OpenWRT/LEDE router: no need for extra hardware
18 | * Integrates it's administration with Ubus
19 | * Has a command-line interface for listing, creating and removing vouchers
20 | * Voucher database is shared among nodes in the network
21 |
22 | All planned features are accesible at: https://github.com/libremesh/voucher/issues
23 |
24 | ## Prerequisites
25 |
26 | This software assumes that will be running on a OpenWRT/LEDE distribution (because uses uci for config). Needs `ip6tables-mod-nat` and `ipset` packages installed.
27 |
28 | ## Install
29 |
30 | Not clear yet, but would be something like:
31 | * add the libremesh software feed to opkg
32 | * opkg install pirania
33 | * opkg install pirania-app
34 |
35 | # How it works
36 |
37 | It uses iptables rules to filter inbound connections outside the mesh network.
38 |
39 | ## General overview of file hierarchy and function
40 |
41 | ```
42 | files/
43 | /etc/config/pirania is the UCI config
44 | /etc/pirania/db.csv (default path) contains the database of vouchers
45 | /etc/init.d/pirania-uhttpd starts a uhttpd on port 59080 that replies any request with a redirect towards a preset URL
46 |
47 | /usr/lib/lua/voucher/ contains lua libraries used by /usr/bin/voucher
48 | /usr/bin/voucher is a CLI to manage the db (has functions add_voucher, add_many_vouchers, auth_voucher, get_valid_macs, list_vouchers, remove_voucher and url)
49 | /usr/bin/captive-portal sets up iptables rules to capture traffic
50 |
51 | /usr/libexec/rpcd/pirania ubus pirania API (this is used by the web frontend)
52 | /usr/share/rpcd/acl.d/pirania.json ACL for the ubus pirania API
53 |
54 | luasrc/ contains the luci-app to manage vouchers
55 | ```
56 |
--------------------------------------------------------------------------------
/pirania-app/files/etc/pirania/content.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Pirania",
3 | "backgroundColor": "#f0e5de",
4 | "welcome": "Welcome, this is a captive portal",
5 | "body": "In order to have access to Interet you must enter a valid voucher. In the meantime you can continue using the local network services.",
6 | "logo": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAABrCAMAAACi04sXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgTWFjaW50b3NoIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjc0MjEyOEY5NUEyOTExRTlBNTMwOTU0MjBCQTA4RDNCIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjc0MjEyOEZBNUEyOTExRTlBNTMwOTU0MjBCQTA4RDNCIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzQyMTI4Rjc1QTI5MTFFOUE1MzA5NTQyMEJBMDhEM0IiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzQyMTI4Rjg1QTI5MTFFOUE1MzA5NTQyMEJBMDhEM0IiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4ENYiSAAAAM1BMVEUQEBBAQEDv7+9/f3+/v79gYGDPz8+fn5/f398wMDAgICBwcHCvr6+Pj49QUFAAAAD///9/TN52AAAAEXRSTlP/////////////////////ACWtmWIAAAttSURBVHjazFvZguMoDOQGA8b9/1+7SOL0Fbund6b90JvJJnahoyQVhH1993J8y9cSzNf/cLFvf1Nu5eLql8HSEi22afF7YNltc19fwvqMi62/BpZgm8YXEYAl8UtgfYVtiwQwv9q8+SWwXHZesZHJBmPqd8CCoOfVoykbTP4OWF/LtoX6eoWM/B2wDKvhlS+V/2H/GSwlpVU17WIPLwJp/xGsQMzupankxb/+B1xvYbFacjaW1mw1PiIx+W3zT2BlJliV1R3dlIDZeMz9C1jgKKg0ppTDbfZb5oxF/IuQB1ze4qONlYHLOZrSz9DEe4IwCwTWFXWKhaz593lLSH9DnWBN83dhOSuBtZRKNymXuWz5q7D0Nl7qhtrkX4RlJlT8MuGEf8WquV1j9k+sBbG+SOOyF2+jR71hL3KB+wNYQiKL8mtMgJhCjz+85wojyjEi3mWiWJEdjv4TKvLJx9v6yF4O6NchLDPWsdcEEQ/RLqLcQaIrxUcuNGCxfF/whP8erBjAWl6M9muQFk7soaxMnvqMT5FPY8qSGQWq6QLwXsMqobXx7h5XOIPLuAs4tyIyHz9FlgJH2kzCi1t6RD6HBWUFAPTnKwTlwwWDKbTj7aTmwW0hByvLLlAD3z2HlZsDFoY4NvhUfmcOBAaTWnY1XFIcAl4CNs3BanrgCfYq1rvgINBSy6chDAKZGbfU1nHd+9AANg4l1o119kVsxYSxZepg+KhVANcvgE3LdCgOemM0MpEvhzL7LhPXQsio1izmeUhib4gJwsxYNhJ2aODCHF78u3Tq0UIl+C9iWWkO1uGr6M5fxum7BZDAu4ELAq7UfhOWRfcjKhbvR6PeKsqxLKBPh9ppABvelHUufQtLggsR1aUQgvm6KhVDaRXd3BZCeyH7Z/Nfjh+bjfUalkFUl2NE7MEDSajhqXP3pXq/wDGapG4MplaZONfqvRPTLSoxmgbaZ20PvWpoRDAAzr1cCG3K0+9gCfriNXHriTQMO2tj4SaiQFFjiiNR8BTAzXtYbtXJ3jV4dwOX2JlGbWNXMITCWqCI4b025+Xw4+zgpruGWN+rRWrfxfNtkJrGHotGymV8j9u+uMT239jYdfB80rDk3mNgriOTcDITG9dg3HgXy47hqNjFw+0nZS0NbmkIYlOgRH+KwqRVFwGcO4r9XVRJoW+gyllebqdUJwxyouDdBxRcYWNXa5Nf7GxxcTsZqQBVus/UOlnwFp6ivkVVtN2J+pmLEpq73wOsUCHEI6oPYowp38YE5LMB+SDuKFifOos6KppmT6eyrkjvE9t8RtUSMQyjXyov0iCFKQCtz3woa4vBDg93tVeIOzt+HElVMQdZZp2SU86wgjiJU9ynIe+zQ+cSaqxq6kJzs6ujvRMdBli+w5L1PrI5dms9xSoP2kocN4/YoWD5KSiW1qeEz7Vpo4kqzJaRdcF1ZTlCItvP3WhO7c47CNfiknZ0oDgngvZAeyyfigMsO7zoabClg/H5NOmxIwPzIa2oJcKN1gfaYyVP37OuE79KQQzKDz7FTZk+pBQ7YSfXZ7gaAtdlZ61PIwy6BlJJkOWoz5GP4V09Kkh6xMVOhPdQPyaIYmqTEi6E1HY3U31tZFFG3JGDqTuSpfAPjhtxMRz48kDg+lpYH8S/moR2vkWR31x8f7Df21QfG5JQux0SCoahYGgZWWotoZuDnnLINpVUzcNUfW+Fb5ghBOxRQBxHN1tbwwDGWcEJYuhQdN8sCSQElUmlamaCvKYbGjtrNeU++BE+1NkBF84TaZqmbSWbEuIwcLN2aEHXvGXkM1JjcFVtBiB4EAolMvUhTiR+xHUoOICEYSpc5s6TyD7jyTFRkkKO+koqK2SVP4WsFaNuXnLydOwkdNgKIP7IeP3Uv4OEIlDPwRgmESWP+4Fkr2x9+FglLlcEqoXrRk+sVxXMKi1a0HN6JpUNHAzdfisA+INHsGhrNMUok9dhxPpZOgdUcpLz2FxMmO7hj3dcnClINcGC6lgdqXYzRIaVb2hK1tbVF6NsXu10RMYxivl60mcqGhLLQpgbidJieJbBt3go/0fgDjmcDZG7VWaDSJjl+ZylLj/jRNM1ehgFP2wXyPFDZbTD0C4MT76EGTkHCjbaM285ny2YQ/3jgQPLKd3j8nn7hZUpqz6Lzhmhh6mc1dkP0iqN1NI2UhS8BZ5P7pPwStqhYx9le1botjPeygpTESxbsx9IqOSpGTsOT/MSpluy530Geq9LnOERLCoDrdfJ0Sd68Q9U5pwtNItu5mN1sWXDmtLtqMY73GPwg3Cqn8EirX7eusSDM1JBiciL9X1CMGn8JJoLSNSijEkEZbuypuRyUOgfOrErtG6nLVJnVDYEdL115FrM5jKt5kRd9w7gogTy84k4uLV8BqvEessmHNc6ss3rOCWFHJfuqRaGJq4M5MlGIb/dmomnsCjWa3EjEcpZFPVA9YaIxq0xjC3vJkq1VAp62pjMW3CtR9JQy5PDEmMbiPK5rXwkanOUQVHBRDZb9mo8qZ7jEHY3HOlte7KrN3WnQOasdA++9sprUeFBXT/w1myuD/sHZl0eUfwAy6yhhLbqTU2OG6bKnK+wGOHmzJnEgkLgcn2uEgkGIeVwkA9hDRUek6xQO00yawln2F/TZ50zFkqU+8nX6x64kS17/CSZ3sKigpOHZynLESiHswIUYjrHZgsTiNNqRuaSsIZSUpdQgl0oGwq/sKCwAzdPzkkwyi2227xC6WaF+w+laJkbmLm/odBPm02dG6aTAKV1C+tRTT2DBai4O9FuV7f1bU0S3PnFLT302TBAr3kwgGcPlJdJH/KZlUbX+yeDMIOMLx26spLXPUtkBviz2C64i6skol0XKEG4tZtNCsuQUa0YTZimsZ03jk9g0UThaghUssZbtFmJQj1eyjZoLhgbymliHN+apgtNLeCW2Oc+8GGGhaJSjYZla9vYduh7FcWG7nvcp+bKsD0FvVGUKGoryp8ElGnLVePR+VRWiZDqvti6SEfxgQbjNKPdVH40F46kKMfAkQtyH9QLOH+RrZZv7oR8dD6CwUCUL7PfkEEsnmaeosHFm+pC5spLYYwB4fAN9US55TvamNfKNh0f+e9EGpnO9oGJsBFbE1FNOJ6F2ZtLod8xhaVEhdThFAKvNv78INwEqx78a7DKqeBCov5uMlBkLjK3RK4H0c+jWpDfi9ubY5Zs7hrH9fA2kFF6mvvzTyX+qFHFfQfa6Yylr2XPzwNNsKA/wwIdeFIUJ2SHMgKHe6FSEXiavPNfh2SnaHB3uI3vvgMrkGRgq/7UVkf6Z35I+iRROooEFr+MwRwptIIay/riRCobV8sqKoBlmkcd3ld+ksALuY1CWCwK7+uj9GyeFTAXSTdZeyh5KkSfdmp5ef5efFPvD+6yQb3OxsLNnoCwUqcD+Uw4aMJJPKv+34MFCY5jmaXN+Km1WtkjXO28GmlS5gdgSapnzOJmhAG7rbMj1u2TDUwftXAhfnU/AKtsT1Hx90dKx2yQdyQx1K5yCM0nqTn3ueTGb8Gi+a/0oXz8+cdOhb87WSdGlZwOGQ8jcPxOyJtaryV1OuwsNEgOUndRP37PRFD48rUktt1V1PtS3WrQBar8P9P9GTd7+c31zZH1My4SmrHrX6vRkb9LV9rt5Cxw6d/YH8H6dBlNA5a5wnU+CIbL3vZnYDUp/dyXUNrPzp3I/x0WGKVIbCfhj2fHjqZM/7MTm/7OrpDpE0e6Pw3551ckod2ve9tAVfTqrO/5G7CaYrWxZN2czXO6uvTqNwd/DAt+Z1rGTJ/WoYdBImEBWi6n9KuW+UdgkXrFmiAS5EoSupl+vPHqZ6g/A4uEuzT+hCvjq0ozgnrXoP4cLNqCkpqzw4F++bq/+VlYvUJLTleSMn7jR2X/CTAA8COGKzk+uG4AAAAASUVORK5CYII=",
7 | "rules": "
Welcome to our community network. It's a communication network owned and managed by the community. In case of any problems first learn how to use our app which can help you find any problems and check the status of the whole network.
",
8 | "mediaUrl": ""
9 | }
--------------------------------------------------------------------------------
/pirania-app/files/usr/libexec/rpcd/pirania-app:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env lua
2 | --[[
3 | Copyright 2018 Marcos Gutierrez
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 | http://www.apache.org/licenses/LICENSE-3.0
8 | ]]--
9 |
10 | require "ubus"
11 | local json = require 'luci.json'
12 | local path = '/etc/pirania/content.json'
13 |
14 | local function printJson (obj)
15 | print(json.encode(obj))
16 | end
17 |
18 | local conn = ubus.connect()
19 | if not conn then
20 | error("Failed to connect to ubus")
21 | end
22 |
23 | local function shell(command)
24 | local handle = io.popen(command)
25 | local result = handle:read("*a")
26 | handle:close()
27 | return result
28 | end
29 |
30 | local function get_clients(msg)
31 | local result = {}
32 | result.clients = {}
33 | local output = shell("for ip in $(ip n show | grep -v IP | awk '{print $1}' | sort -ut '|' -k 1,2); do grep $ip /tmp/dhcp.leases; done")
34 | for line in output:gmatch("[^\n]+") do
35 | local words = {}
36 | for w in line:gmatch("%S+") do if w ~= "" then table.insert(words, w) end end
37 | local mac = words[2]
38 | local ip = words[3]
39 | local station = words[4]
40 | table.insert(result.clients, { station=station, ip=ip, mac=mac })
41 | end
42 | printJson(result);
43 | end
44 |
45 | local function read_content(msg)
46 | local contents = ""
47 | local myTable = {}
48 | local file = io.open( path, "r" )
49 |
50 | if file then
51 | -- read all contents of file into a string
52 | local contents = file:read( "*a" )
53 | myTable = json.decode(contents)
54 | io.close( file )
55 | end
56 | printJson(myTable)
57 | end
58 |
59 | local function write_content(msg)
60 | local file = io.open(path, "w")
61 | if file then
62 | local contents = json.encode(msg)
63 | file:write( contents )
64 | io.close( file )
65 | read_content()
66 | end
67 | end
68 |
69 | local methods = {
70 | read_content = { no_params = 0 },
71 | get_clients = { no_params = 0 },
72 | write_content = {
73 | title = 'value',
74 | backgroundColor = 'value',
75 | welcome = 'value',
76 | body = 'value',
77 | logo = 'value',
78 | rules = 'value'
79 | }
80 | }
81 |
82 | if arg[1] == 'list' then
83 | printJson(methods)
84 | end
85 |
86 | if arg[1] == 'call' then
87 | local msg = io.read()
88 | msg = json.decode(msg)
89 | if arg[2] == 'read_content' then read_content(msg)
90 | elseif arg[2] == 'get_clients' then get_clients(msg)
91 | elseif arg[2] == 'write_content' then write_content(msg)
92 | else printJson({ error = "Method not found" })
93 | end
94 | end
--------------------------------------------------------------------------------
/pirania-app/files/usr/share/rpcd/acl.d/pirania-app.json:
--------------------------------------------------------------------------------
1 | {
2 | "unauthenticated": {
3 | "description": "Pirania public access",
4 | "write": {
5 | "ubus": {
6 | "pirania-app": [ "get_clients", "read_content" ]
7 | }
8 | }
9 | },
10 | "root": {
11 | "description": "Pirania administration access",
12 | "write": {
13 | "ubus": {
14 | "pirania-app": ["*"]
15 | }
16 | },
17 | "read": {
18 | "ubus": {
19 | "pirania-app": ["*"]
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/pirania-app/files/www/cgi-bin/error_handler:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | print ("Content-type: text/html\n\n")
4 |
5 | print([[
6 |
7 |
8 |
9 | Pirania
10 |
11 |
12 |
15 |
16 |
17 |
18 | ENTER
19 |
20 |
21 | ]])
--------------------------------------------------------------------------------
/pirania-app/files/www/cgi-bin/pirania/client_ip:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local json = require 'luci.json'
4 | local voucher = require('voucher.logic')
5 |
6 | print ("Content-type: application/json\n")
7 |
8 |
9 | local ipv4AndMac = voucher.getIpv4AndMac()
10 |
11 |
12 | local response = {}
13 | response.ip = ipv4AndMac.ip
14 | response.mac = ipv4AndMac.mac
15 | response.valid = voucher.check_mac_validity(ipv4AndMac.mac) > 0
16 |
17 | print(json.encode(response))
18 |
--------------------------------------------------------------------------------
/pirania-app/files/www/cgi-bin/pirania/handle_auth:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local http = require('luci.http')
4 | local logic = require('voucher.logic')
5 | local dba = require('voucher.db')
6 | local query_string = os.getenv("QUERY_STRING")
7 | local db = dba.load(config.db)
8 | local redirect_page = require('voucher.utils').redirect_page
9 | local uci_cursor = require('uci').cursor()
10 |
11 | local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated")
12 | local url_fail = uci_cursor:get("pirania", "base_config", "url_fail")
13 |
14 | print("Content-type: text/html \n\n")
15 |
16 | local res = logic.getIpv4AndMac()
17 | params = http.urldecode_params(query_string)
18 | local voucher = params['voucher']
19 | local mac = res.mac
20 | local prevUrl = params['prev']
21 | local valid = logic.check_mac_validity(res.mac)
22 |
23 | local url = url_fail
24 | local output
25 | local success = false
26 |
27 | if (voucher and mac) then
28 | local res = logic.auth_voucher(db, mac, voucher)
29 | if (res.success == true or valid > 0) then
30 | if (res.success == true) then
31 | success = true
32 | end
33 | if (prevUrl ~= nil) then
34 | url = 'http://'..prevUrl
35 | else
36 | url = url_authenticated
37 | end
38 | end
39 | end
40 |
41 | print(redirect_page(url))
42 |
43 |
44 | if (success == true) then
45 | dba.save(config.db, db)
46 | end
--------------------------------------------------------------------------------
/pirania-app/files/www/cgi-bin/pirania/handle_voucher:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local http = require('luci.http')
4 | local logic = require('voucher.logic')
5 | local redirect_page = require('voucher.utils').redirect_page
6 | local uci_cursor = require('uci').cursor()
7 | local dba = require('voucher.db')
8 | local query_string = os.getenv("QUERY_STRING")
9 | -- send("Status: 302 \r\n")
10 | print("Content-type: text/html \n\n")
11 | params = http.urldecode_params(query_string)
12 | local res = logic.getIpv4AndMac()
13 | local valid = logic.check_mac_validity(res.mac)
14 | local noJs = params['nojs']
15 |
16 | local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated")
17 | local url_fail = uci_cursor:get("pirania", "base_config", "url_fail")
18 | local url_info = uci_cursor:get("pirania", "base_config", "url_info")
19 |
20 | local url
21 |
22 | if (valid < 1) then
23 | local db = dba.load(config.db)
24 | local output
25 | local mac = res.mac
26 | local voucher = params['voucher']
27 | local prevUrl = params['prev']
28 | if (noJs == 'true') then
29 | local authCommand = "voucher auth_voucher " .. mac .. " ".. voucher
30 | ac = io.popen(authCommand, 'r')
31 | output = ac:read('*l')
32 | ac:close()
33 | local validationOut = tonumber(output)
34 | if (validationOut and validationOut > 0) then
35 | url = url_authenticated
36 | else
37 | url = url_fail
38 | end
39 | else
40 | local setParams = prevUrl and '?voucher='..voucher..'&prev='..prevUrl or'?voucher='..voucher
41 | local voucherValid = logic.check_voucher_validity(voucher, db)
42 | if (voucherValid.valid) then
43 | url = url_info..setParams
44 | else
45 | url = url_fail
46 | end
47 | end
48 |
49 |
50 |
51 | else
52 | url = url_authenticated
53 | end
54 |
55 | print(redirect_page(url))
56 |
57 |
--------------------------------------------------------------------------------
/pirania-app/files/www/cgi-bin/pirania/portal:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local http = require('luci.http')
4 | local logic = require('voucher.logic')
5 | local redirect_page = require('voucher.utils').redirect_page
6 | local uci_cursor = require('uci').cursor()
7 |
8 | local query_string = os.getenv("QUERY_STRING")
9 | print("Content-type: text/html \n\n")
10 | params = http.urldecode_params(query_string)
11 | local prevUrl = params['prev']
12 |
13 | local url_auth = uci_cursor:get("pirania", "base_config", "url_auth")
14 | local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated")
15 |
16 | local res = logic.getIpv4AndMac()
17 | local valid = logic.check_mac_validity(res.mac)
18 |
19 | local url
20 | local setParams = prevUrl and '?prev='..prevUrl or ''
21 |
22 | if (valid > 0) then
23 | url = url_authenticated..setParams
24 | else
25 | url = url_auth..setParams
26 | end
27 |
28 | print(redirect_page(url))
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/admin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pirania | Admin
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
26 |
27 |
55 |
56 |
57 |
59 |
60 |
61 |
62 |
70 |
71 |
72 |
94 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/auth.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pirania
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/authenticated.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pirania
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
✅
32 |
Authenticated!
33 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/css/loader2.css:
--------------------------------------------------------------------------------
1 | .lds-ring {
2 | display: inline-block;
3 | /* position: relative; */
4 | top: -6.5vh;
5 | width: 25px;
6 | height: 25px;
7 | margin: 0 auto;
8 | text-align: center;
9 | margin-bottom: -6.5vh;
10 | left: 200px;
11 | }
12 |
13 | .lds-ring div {
14 | box-sizing: border-box;
15 | display: block;
16 | position: absolute;
17 | width: 20px;
18 | height: 20px;
19 | margin: 2px;
20 | border: 2px solid #000;
21 | border-radius: 50%;
22 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
23 | border-color: #000 transparent transparent transparent;
24 | }
25 |
26 | .lds-ring div:nth-child(1) {
27 | animation-delay: -0.45s;
28 | }
29 |
30 | .lds-ring div:nth-child(2) {
31 | animation-delay: -0.3s;
32 | }
33 |
34 | .lds-ring div:nth-child(3) {
35 | animation-delay: -0.15s;
36 | }
37 |
38 | @keyframes lds-ring {
39 | 0% {
40 | transform: rotate(0deg);
41 | }
42 |
43 | 100% {
44 | transform: rotate(360deg);
45 | }
46 | }
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | border: 10px solid white;
3 | }
4 |
5 | a {
6 | text-decoration: none;
7 | }
8 |
9 | input[type=number] {
10 | /*for absolutely positioning spinners*/
11 | position: relative;
12 | padding: 5px;
13 | padding-right: 25px;
14 | }
15 |
16 | input[type=number]::-webkit-inner-spin-button,
17 | input[type=number]::-webkit-outer-spin-button {
18 | opacity: 1;
19 | }
20 |
21 |
22 | input[type=number]::-webkit-outer-spin-button,
23 | input[type=number]::-webkit-inner-spin-button {
24 | -webkit-appearance: inner-spin-button !important;
25 | width: 25px;
26 | position: absolute;
27 | top: 0;
28 | right: 0;
29 | height: 100%;
30 | }
31 |
32 |
33 | input[type=text],
34 | input[type=number] {
35 | padding: 5px;
36 | border: 2px solid #ccc;
37 | -webkit-border-radius: 15px;
38 | border-radius: 15px;
39 | }
40 |
41 | input[type=text]:focus,
42 | input[type=number]:focus {
43 | border-color: #333;
44 | }
45 |
46 | input[type=submit],
47 | button {
48 | padding: 10px 25px;
49 | background: #ccc;
50 | border: 0 none;
51 | cursor: pointer;
52 | -webkit-border-radius: 5px;
53 | border-radius: 5px;
54 | }
55 |
56 | input,
57 | textarea {
58 | max-width: 100%;
59 | padding: 5px;
60 | border: 2px solid #ccc;
61 | -webkit-border-radius: 15px;
62 | border-radius: 15px;
63 | }
64 |
65 | #link input {
66 | margin-top: 15vh;
67 | }
68 |
69 | video {
70 | min-width: 100%;
71 | }
72 |
73 | #content-media {
74 | margin: 0 auto;
75 | padding: 5vh 0;
76 | }
77 |
78 | #content-media img {
79 | max-width: 300px;
80 | }
81 |
82 | #user-valid {
83 | font-size: 45px;
84 | }
85 |
86 | select {
87 | display: block;
88 | font-family: sans-serif;
89 | color: #444;
90 | line-height: 1.3;
91 | padding: .6em 1.4em .5em .8em;
92 | width: 100%;
93 | max-width: 100%;
94 | box-sizing: border-box;
95 | margin: 0;
96 | border: 1px solid #aaa;
97 | box-shadow: 0 1px 0 1px rgba(0, 0, 0, .04);
98 | border-radius: .5em;
99 | -moz-appearance: none;
100 | -webkit-appearance: none;
101 | appearance: none;
102 | background-color: #fff;
103 | background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-6.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E'),
104 | linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
105 | background-repeat: no-repeat, repeat;
106 | background-position: right .7em top 50%, 0 0;
107 | background-size: .65em auto, 100%;
108 | }
109 |
110 | select::-ms-expand {
111 | display: none;
112 | }
113 |
114 | select:hover {
115 | border-color: #888;
116 | }
117 |
118 | select:focus {
119 | border-color: #aaa;
120 | box-shadow: 0 0 1px 3px rgba(59, 153, 252, .7);
121 | box-shadow: 0 0 0 3px -moz-mac-focusring;
122 | color: #222;
123 | outline: none;
124 | }
125 |
126 | select option {
127 | font-weight: normal;
128 | }
129 |
130 | .main {
131 | margin: 0 auto;
132 | max-width: 968px;
133 | padding: 5%;
134 | text-align: center;
135 | animation: ease-in;
136 | min-height: 90vh;
137 | }
138 |
139 | /* Tabs */
140 | .tab-nav {
141 | margin: 0;
142 | padding: 0;
143 | }
144 |
145 | .tab-link {
146 | margin-right: 5px;
147 | padding: 10px 20px;
148 | color: #fff;
149 | display: inline-block;
150 | background-color: #b3b3b3;
151 | }
152 |
153 | .tab-link:last-child {
154 | margin-right: 0;
155 | }
156 |
157 | .tab-link:hover {
158 | color: #6d6d6d;
159 | }
160 |
161 | .tab-link.is-active {
162 | color: lightcoral;
163 | background-color: #e7e7e7;
164 | }
165 |
166 | /* Tab Content */
167 | .tab-contents svg {
168 | height: 15px;
169 | width: 15px;
170 | }
171 |
172 | .tab-content {
173 | display: none;
174 | /* background-color: #e7e7e7; */
175 | }
176 |
177 | .tab-content.is-active {
178 | display: block;
179 | padding: 20px;
180 | }
181 |
182 | .content {
183 | max-width: 968px;
184 | margin: 0 auto;
185 | }
186 |
187 | .login {
188 | position: absolute;
189 | top: 15px;
190 | left: 15px;
191 | text-decoration: none;
192 | color: black;
193 | }
194 |
195 | .voucher {
196 | padding-top: 30px;
197 | }
198 |
199 | .voucher form {
200 | display: flex;
201 | flex-flow: column;
202 | align-items: center;
203 | justify-content: space-around;
204 | height: 33vh;
205 | }
206 |
207 | .voucher form * {
208 | width: 250px;
209 | }
210 |
211 | .admin-login,
212 | #tabs {
213 | margin-top: 50px;
214 | padding: 10px;
215 | }
216 |
217 | .admin-login {
218 | border: 1px solid #444;
219 | }
220 |
221 | .admin-login form,
222 | .admin-create form,
223 | .admin-content form,
224 | .admin-create-many form {
225 | display: flex;
226 | flex-flow: column;
227 | align-items: center;
228 | align-content: center;
229 | justify-content: space-around;
230 | margin: 0 auto;
231 | max-width: 300px;
232 | min-height: 20vh;
233 | }
234 |
235 | .admin-create form {
236 | min-height: 35vh;
237 | }
238 |
239 | .admin-create-many form {
240 | min-height: 45vh;
241 | }
242 |
243 | .admin-content form {
244 | min-height: 100vh;
245 | }
246 |
247 | .voucher-item svg {
248 | width: 12px;
249 | height: 12px;
250 | }
251 |
252 | .voucher-item {
253 | position: relative;
254 | display: flex;
255 | flex-flow: row nowrap;
256 | justify-content: space-between;
257 | align-items: center;
258 | align-content: flex-start;
259 | padding: 15px 0;
260 | }
261 |
262 | .voucher-item-name,
263 | .voucher-item-voucher {
264 | width: 30%
265 | }
266 |
267 | .voucher-item-expires,
268 | .voucher-item-remove,
269 | .voucher-item-renew {
270 | width: 10%;
271 | }
272 |
273 | .voucher-item-voucher {
274 | display: none;
275 | font-family: monospace;
276 | color: #6d6d6d;
277 | }
278 |
279 | .voucher-item-mq {
280 | position: absolute;
281 | left: -20px;
282 | background: #444;
283 | border-radius: 50%;
284 | padding: 0 5px;
285 | color: white;
286 | }
287 |
288 | .voucher-item-macs {
289 | display: none;
290 | }
291 |
292 | .voucher-item-name:hover~.voucher-item-macs {
293 | display: block;
294 | position: absolute;
295 | bottom: -5px;
296 | }
297 |
298 | #voucher-list-button {
299 | position: fixed;
300 | top: 5%;
301 | right: 5%;
302 | border-radius: 25px;
303 | z-index: 999;
304 | }
305 |
306 | #voucher-list-loader {
307 | position: absolute;
308 | top: 50%;
309 | left: 50%;
310 | }
311 |
312 | #error, .error {
313 | margin: 0 auto;
314 | text-align: center;
315 | padding: 10px 0;
316 | background: darkseagreen;
317 | border-radius: 8px;
318 | background: indianred;
319 | }
320 |
321 | .hidden {
322 | position: absolute !important;
323 | top: -9999px !important;
324 | left: -9999px !important;
325 | z-index: -1;
326 | }
327 |
328 | #other-devices {
329 | position: absolute;
330 | top: 30px;
331 | right: 30px;
332 | margin: 0 auto;
333 | border-radius: 25px;
334 | height: 45px;
335 | width: 45px;
336 | display: flex;
337 | flex-flow: row nowrap;
338 | text-align: center;
339 | justify-content: center;
340 | }
341 |
342 | #other-devices span {
343 | font-size: 20px;
344 | position: relative;
345 | right: 10px;
346 | }
347 |
348 | @media only screen and (max-width: 480px) {
349 | .voucher-item-name:hover~.voucher-item-voucher {
350 | display: block;
351 | position: absolute;
352 | top: -5px;
353 | }
354 | }
355 |
356 | @media only screen and (min-width: 480px) {
357 | .tab-contents svg {
358 | height: 32px;
359 | width: 32px;
360 | }
361 |
362 | .voucher-item svg {
363 | width: 20px;
364 | height: 20px;
365 | }
366 |
367 | .voucher-item-voucher {
368 | display: block;
369 | }
370 | }
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/css/mobile-icons.css:
--------------------------------------------------------------------------------
1 | .mobile.icon {
2 | color: #000;
3 | position: absolute;
4 | margin-left: 4px;
5 | margin-top: 0px;
6 | width: 12px;
7 | height: 19px;
8 | border-radius: 2px;
9 | border: solid 1px currentColor;
10 | }
11 |
12 | .mobile.icon:before {
13 | content: '';
14 | position: absolute;
15 | left: 5px;
16 | top: 1px;
17 | width: 2px;
18 | height: 1px;
19 | background-color: currentColor;
20 | }
21 |
22 | .mobile.icon:after {
23 | content: '';
24 | position: absolute;
25 | bottom: 1px;
26 | left: 5px;
27 | height: 2px;
28 | width: 2px;
29 | border-radius: 50%;
30 | background-color: currentColor;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/css/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15;
13 | /* 1 */
14 | -webkit-text-size-adjust: 100%;
15 | /* 2 */
16 | }
17 |
18 | /* Sections
19 | ========================================================================== */
20 |
21 | /**
22 | * Remove the margin in all browsers.
23 | */
24 |
25 | body {
26 | margin: 0;
27 | }
28 |
29 | /**
30 | * Render the `main` element consistently in IE.
31 | */
32 |
33 | main {
34 | display: block;
35 | }
36 |
37 | /**
38 | * Correct the font size and margin on `h1` elements within `section` and
39 | * `article` contexts in Chrome, Firefox, and Safari.
40 | */
41 |
42 | h1 {
43 | font-size: 2em;
44 | margin: 0.67em 0;
45 | }
46 |
47 | /* Grouping content
48 | ========================================================================== */
49 |
50 | /**
51 | * 1. Add the correct box sizing in Firefox.
52 | * 2. Show the overflow in Edge and IE.
53 | */
54 |
55 | hr {
56 | box-sizing: content-box;
57 | /* 1 */
58 | height: 0;
59 | /* 1 */
60 | overflow: visible;
61 | /* 2 */
62 | }
63 |
64 | /**
65 | * 1. Correct the inheritance and scaling of font size in all browsers.
66 | * 2. Correct the odd `em` font sizing in all browsers.
67 | */
68 |
69 | pre {
70 | font-family: monospace, monospace;
71 | /* 1 */
72 | font-size: 1em;
73 | /* 2 */
74 | }
75 |
76 | /* Text-level semantics
77 | ========================================================================== */
78 |
79 | /**
80 | * Remove the gray background on active links in IE 10.
81 | */
82 |
83 | a {
84 | background-color: transparent;
85 | }
86 |
87 | /**
88 | * 1. Remove the bottom border in Chrome 57-
89 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
90 | */
91 |
92 | abbr[title] {
93 | border-bottom: none;
94 | /* 1 */
95 | text-decoration: underline;
96 | /* 2 */
97 | text-decoration: underline dotted;
98 | /* 2 */
99 | }
100 |
101 | /**
102 | * Add the correct font weight in Chrome, Edge, and Safari.
103 | */
104 |
105 | b,
106 | strong {
107 | font-weight: bolder;
108 | }
109 |
110 | /**
111 | * 1. Correct the inheritance and scaling of font size in all browsers.
112 | * 2. Correct the odd `em` font sizing in all browsers.
113 | */
114 |
115 | code,
116 | kbd,
117 | samp {
118 | font-family: monospace, monospace;
119 | /* 1 */
120 | font-size: 1em;
121 | /* 2 */
122 | }
123 |
124 | /**
125 | * Add the correct font size in all browsers.
126 | */
127 |
128 | small {
129 | font-size: 80%;
130 | }
131 |
132 | /**
133 | * Prevent `sub` and `sup` elements from affecting the line height in
134 | * all browsers.
135 | */
136 |
137 | sub,
138 | sup {
139 | font-size: 75%;
140 | line-height: 0;
141 | position: relative;
142 | vertical-align: baseline;
143 | }
144 |
145 | sub {
146 | bottom: -0.25em;
147 | }
148 |
149 | sup {
150 | top: -0.5em;
151 | }
152 |
153 | /* Embedded content
154 | ========================================================================== */
155 |
156 | /**
157 | * Remove the border on images inside links in IE 10.
158 | */
159 |
160 | img {
161 | border-style: none;
162 | }
163 |
164 | /* Forms
165 | ========================================================================== */
166 |
167 | /**
168 | * 1. Change the font styles in all browsers.
169 | * 2. Remove the margin in Firefox and Safari.
170 | */
171 |
172 | button,
173 | input,
174 | optgroup,
175 | select,
176 | textarea {
177 | font-family: inherit;
178 | /* 1 */
179 | font-size: 100%;
180 | /* 1 */
181 | line-height: 1.15;
182 | /* 1 */
183 | margin: 0;
184 | /* 2 */
185 | }
186 |
187 | /**
188 | * Show the overflow in IE.
189 | * 1. Show the overflow in Edge.
190 | */
191 |
192 | button,
193 | input {
194 | /* 1 */
195 | overflow: visible;
196 | }
197 |
198 | /**
199 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
200 | * 1. Remove the inheritance of text transform in Firefox.
201 | */
202 |
203 | button,
204 | select {
205 | /* 1 */
206 | text-transform: none;
207 | }
208 |
209 | /**
210 | * Correct the inability to style clickable types in iOS and Safari.
211 | */
212 |
213 | button,
214 | [type="button"],
215 | [type="reset"],
216 | [type="submit"] {
217 | -webkit-appearance: button;
218 | }
219 |
220 | /**
221 | * Remove the inner border and padding in Firefox.
222 | */
223 |
224 | button::-moz-focus-inner,
225 | [type="button"]::-moz-focus-inner,
226 | [type="reset"]::-moz-focus-inner,
227 | [type="submit"]::-moz-focus-inner {
228 | border-style: none;
229 | padding: 0;
230 | }
231 |
232 | /**
233 | * Restore the focus styles unset by the previous rule.
234 | */
235 |
236 | button:-moz-focusring,
237 | [type="button"]:-moz-focusring,
238 | [type="reset"]:-moz-focusring,
239 | [type="submit"]:-moz-focusring {
240 | outline: 1px dotted ButtonText;
241 | }
242 |
243 | /**
244 | * Correct the padding in Firefox.
245 | */
246 |
247 | fieldset {
248 | padding: 0.35em 0.75em 0.625em;
249 | }
250 |
251 | /**
252 | * 1. Correct the text wrapping in Edge and IE.
253 | * 2. Correct the color inheritance from `fieldset` elements in IE.
254 | * 3. Remove the padding so developers are not caught out when they zero out
255 | * `fieldset` elements in all browsers.
256 | */
257 |
258 | legend {
259 | box-sizing: border-box;
260 | /* 1 */
261 | color: inherit;
262 | /* 2 */
263 | display: table;
264 | /* 1 */
265 | max-width: 100%;
266 | /* 1 */
267 | padding: 0;
268 | /* 3 */
269 | white-space: normal;
270 | /* 1 */
271 | }
272 |
273 | /**
274 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
275 | */
276 |
277 | progress {
278 | vertical-align: baseline;
279 | }
280 |
281 | /**
282 | * Remove the default vertical scrollbar in IE 10+.
283 | */
284 |
285 | textarea {
286 | overflow: auto;
287 | }
288 |
289 | /**
290 | * 1. Add the correct box sizing in IE 10.
291 | * 2. Remove the padding in IE 10.
292 | */
293 |
294 | [type="checkbox"],
295 | [type="radio"] {
296 | box-sizing: border-box;
297 | /* 1 */
298 | padding: 0;
299 | /* 2 */
300 | }
301 |
302 | /**
303 | * Correct the cursor style of increment and decrement buttons in Chrome.
304 | */
305 |
306 | [type="number"]::-webkit-inner-spin-button,
307 | [type="number"]::-webkit-outer-spin-button {
308 | height: auto;
309 | }
310 |
311 | /**
312 | * 1. Correct the odd appearance in Chrome and Safari.
313 | * 2. Correct the outline style in Safari.
314 | */
315 |
316 | [type="search"] {
317 | -webkit-appearance: textfield;
318 | /* 1 */
319 | outline-offset: -2px;
320 | /* 2 */
321 | }
322 |
323 | /**
324 | * Remove the inner padding in Chrome and Safari on macOS.
325 | */
326 |
327 | [type="search"]::-webkit-search-decoration {
328 | -webkit-appearance: none;
329 | }
330 |
331 | /**
332 | * 1. Correct the inability to style clickable types in iOS and Safari.
333 | * 2. Change font properties to `inherit` in Safari.
334 | */
335 |
336 | ::-webkit-file-upload-button {
337 | -webkit-appearance: button;
338 | /* 1 */
339 | font: inherit;
340 | /* 2 */
341 | }
342 |
343 | /* Interactive
344 | ========================================================================== */
345 |
346 | /*
347 | * Add the correct display in Edge, IE 10+, and Firefox.
348 | */
349 |
350 | details {
351 | display: block;
352 | }
353 |
354 | /*
355 | * Add the correct display in all browsers.
356 | */
357 |
358 | summary {
359 | display: list-item;
360 | }
361 |
362 | /* Misc
363 | ========================================================================== */
364 |
365 | /**
366 | * Add the correct display in IE 10+.
367 | */
368 |
369 | template {
370 | display: none;
371 | }
372 |
373 | /**
374 | * Add the correct display in IE 10.
375 | */
376 |
377 | [hidden] {
378 | display: none;
379 | }
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/fail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pirania
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Redirect
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 | ENTER
20 |
21 |
22 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/info.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Pirania
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
85 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/js/admin.js:
--------------------------------------------------------------------------------
1 | let session = null
2 | let uploadedLogo = null
3 |
4 | var errorElem = document.getElementById('error')
5 |
6 | Date.daysBetween = function( date1, date2 ) { //Get 1 day in milliseconds
7 | var one_day=1000*60*60*24 // Convert both dates to milliseconds
8 | var date1_ms = date1.getTime()
9 | var date2_ms = date2.getTime() // Calculate the difference in milliseconds
10 | var difference_ms = date2_ms - date1_ms // Convert back to days and return
11 | return Math.round(difference_ms/one_day)
12 | }
13 |
14 |
15 | function xDaysFromNow (days) {
16 | let date = new Date()
17 | let newDate = date.setDate(date.getDate() + parseInt(days))
18 | return newDate.toString()
19 | }
20 |
21 | function makeid(length) {
22 | var text = ""
23 | var possible = "abcdefghijklmnopqrstuvwxyz0123456789"
24 | for (var i = 0; i < length; i++)
25 | text += possible.charAt(Math.floor(Math.random() * possible.length))
26 | return text
27 | }
28 |
29 | function compress(e) {
30 | const fileName = e.target.files[0].name
31 | const reader = new FileReader()
32 | reader.readAsDataURL(e.target.files[0])
33 | reader.onload = event => {
34 | const img = new Image()
35 | img.src = event.target.result
36 | img.onload = () => {
37 | const elem = document.createElement('canvas')
38 | const width = 50
39 | const scaleFactor = width / img.width
40 | elem.width = width
41 | elem.height = img.height * scaleFactor
42 | const ctx = elem.getContext('2d')
43 | ctx.drawImage(img, 0, 0, width, img.height * scaleFactor)
44 | ctx.canvas.toBlob((blob) => {
45 | const file = new File([blob], fileName, {
46 | type: 'image/jpeg',
47 | lastModified: Date.now()
48 | })
49 | }, 'image/jpeg', 1)
50 | console.log(ctx.canvas.toDataURL("image/jpeg"))
51 | uploadedLogo = ctx.canvas.toDataURL("image/jpeg")
52 | document.getElementById("logo-upload").appendChild(elem)
53 | },
54 | reader.onerror = error => console.log(error)
55 |
56 | }
57 | }
58 |
59 | function createManyVouchers () {
60 | const createLoader = document.getElementById('voucher-create-loader')
61 | const key = document.getElementById('adminManyInputKey').value
62 | const days = document.getElementById('adminManyInputDays').value
63 | const numberVouchers = document.getElementById('adminManyInputVouchers').value
64 | const epoc = xDaysFromNow(days)
65 | const vouchers = []
66 | for (let index = 0; index < numberVouchers; index++) {
67 | vouchers.push({
68 | key: `${key}-${makeid(3)}`,
69 | voucher: makeid(8),
70 | epoc,
71 | })
72 | }
73 | console.log('VOUCHERS GO', JSON.stringify(vouchers))
74 | hide(errorElem)
75 | show(createLoader)
76 | ubusFetch('pirania', 'add_many_vouchers', { vouchers }, session)
77 | .then(res => {
78 | console.log('RESPONSE', res)
79 | if (res.success) {
80 | listVouchers()
81 | console.log('VOUCHERS SUCCESS', res.success)
82 | document.getElementById('adminManyInputKey').value = ''
83 | document.getElementById('adminManyInputDays').value = 1
84 | document.getElementById('adminManyInputVouchers').value = 1
85 | document.getElementById('many-result').innerHTML = 'Sucesso!'
86 | }
87 | })
88 | .catch(err => {
89 | console.log('Can find this error ', err)
90 | listVouchers()
91 | document.getElementById('adminManyInputKey').value = ''
92 | document.getElementById('adminManyInputDays').value = 1
93 | document.getElementById('adminManyInputVouchers').value = 1
94 | document.getElementById('many-result').innerHTML = 'Sucesso!'
95 | // show(errorElem)
96 | hide(createLoader)
97 | })
98 | }
99 |
100 | function removeVoucher (name) {
101 | ubusFetch('pirania', 'remove_voucher', { name }, session)
102 | .then(res => {
103 | console.log(res)
104 | listVouchers()
105 | })
106 | .catch(err => {
107 | console.log(err)
108 | show(errorElem)
109 | })
110 | }
111 |
112 | /* Needs to be implemented in shared-state */
113 | // function renewVoucher (name) {
114 | // console.log(name, xDaysFromNow(30))
115 | // ubusFetch('pirania', 'renew_voucher', { name, date: xDaysFromNow(30) }, session)
116 | // .then(res => console.log(res))
117 | // .catch(err => {
118 | // console.log(err)
119 | // show(errorElem)
120 | // })
121 | // }
122 |
123 | function getVoucherName (name) {
124 | return name.split('-')[0]
125 | }
126 |
127 | function listVouchers () {
128 | let voucherList = document.getElementById("voucher-list")
129 | let listLoader = document.getElementById('voucher-list-loader')
130 | hide(errorElem)
131 | voucherList.innerHTML = ''
132 | show(listLoader)
133 | ubusFetch('pirania', 'list_vouchers', {}, session)
134 | .then(res => {
135 | const vouchers = res.vouchers
136 | // document.getElementById('voucher-list-button').style.display = 'none'
137 | vouchers
138 | .sort((a, b) => {
139 | if(parseInt(a.expires) > parseInt(b.expires)) { return -1; }
140 | if(parseInt(a.expires) < parseInt(b.expires)) { return 1; }
141 | return 0;
142 | })
143 | .sort((a, b) => {
144 | if(getVoucherName(a.name) < getVoucherName(b.name)) { return -1; }
145 | if(getVoucherName(a.name) > getVoucherName(b.name)) { return 1; }
146 | return 0;
147 | })
148 | .map(v => {
149 | const date = new Date (parseInt(v.expires))
150 | const dateDiff = Date.daysBetween(new Date(), date)
151 | let container = document.createElement('div')
152 | container.className = 'voucher-item'
153 | let vQuantity = document.createElement('div')
154 | vQuantity.className = 'voucher-item-mq'
155 | vQuantity.innerHTML = v.macs.length > 0 ? v.macs.length : ''
156 | container.appendChild(vQuantity)
157 | let name = document.createElement('div')
158 | name.className = 'voucher-item-name'
159 | name.innerHTML = v.name
160 | container.appendChild(name)
161 | let voucher = document.createElement('div')
162 | voucher.className = 'voucher-item-voucher'
163 | voucher.innerHTML = v.voucher
164 | container.appendChild(voucher)
165 | /* Needs to be implemented in shared-state */
166 | // let renew = document.createElementNS("http://www.w3.org/2000/svg", "svg")
167 | // renew.setAttribute ("viewBox", "0 0 32 32" )
168 | // renew.setAttribute ("stroke", "currentcolor" )
169 | // renew.setAttribute ("stroke-linecap", "round" )
170 | // renew.setAttribute ("stroke-linejoin", "round" )
171 | // renew.setAttribute ("stroke-width", "2" )
172 | // renew.setAttribute ("fill", "none" )
173 | // renew.className = 'voucher-item-renew'
174 | // renew.onclick = () => renewVoucher(v.name)
175 | // let renewPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
176 | // renewPath.setAttribute('d', 'M29 16 C29 22 24 29 16 29 8 29 3 22 3 16 3 10 8 3 16 3 21 3 25 6 27 9 M20 10 L27 9 28 2')
177 | // renew.appendChild(renewPath)
178 | // container.appendChild(renew)
179 | let remove = document.createElementNS("http://www.w3.org/2000/svg", "svg")
180 | remove.setAttribute ("viewBox", "0 0 32 32" )
181 | remove.setAttribute ("stroke", "currentcolor" )
182 | remove.setAttribute ("stroke-linecap", "round" )
183 | remove.setAttribute ("stroke-linejoin", "round" )
184 | remove.setAttribute ("stroke-width", "2" )
185 | remove.setAttribute ("fill", "none" )
186 | remove.className = 'voucher-item-remove'
187 | remove.onclick = () => removeVoucher(v.name)
188 | let removePath = document.createElementNS("http://www.w3.org/2000/svg", "path")
189 | removePath.setAttribute('d', 'M28 6 L6 6 8 30 24 30 26 6 4 6 M16 12 L16 24 M21 12 L20 24 M11 12 L12 24 M12 6 L13 2 19 2 20 6')
190 | remove.appendChild(removePath)
191 | container.appendChild(remove)
192 | let expires = document.createElement('div')
193 | expires.className = 'voucher-item-expires'
194 | expires.innerHTML = dateDiff > 0 ? dateDiff +' '+ int[lang].days : 0
195 | container.appendChild(expires)
196 | let macs = document.createElement('div')
197 | macs.innerHTML = v.macs.toString()
198 | macs.className = 'voucher-item-macs'
199 | listLoader.className = 'hidden'
200 | container.appendChild(macs)
201 | voucherList.appendChild(container)
202 | document.getElementById('voucher-list-button').classList.remove('hidden')
203 | })
204 | })
205 | .catch(err => {
206 | console.log(err)
207 | hide(listLoader)
208 | show(document.getElementById('voucher-list-button'))
209 | errorElem.innerHTML = int[lang].error
210 | show(errorElem)
211 | })
212 | }
213 |
214 | function updateContent () {
215 | const backgroundColor = document.getElementById('adminInputBackground').value
216 | const title = document.getElementById('adminInputTitle').value
217 | const welcome = document.getElementById('adminInputWelcome').value
218 | const body = document.getElementById('adminInputBody').value
219 | let logo = uploadedLogo || content.logo
220 | ubusFetch(
221 | 'pirania-app',
222 | 'write_content',
223 | {
224 | backgroundColor,
225 | title,
226 | welcome,
227 | body,
228 | logo,
229 | },
230 | session
231 | )
232 | .then(res => {
233 | content = res
234 | const { backgroundColor, title, welcome, body, logo } = content
235 | document.body.style.backgroundColor = backgroundColor
236 | const contentLogo = document.getElementById('content-logo')
237 | const contentTitle = document.getElementById('content-title')
238 | const contentWelcome = document.getElementById('content-welcome')
239 | const contentBody = document.getElementById('content-body')
240 | if (contentLogo) contentLogo.src = logo
241 | if (contentTitle) contentTitle.innerHTML = title
242 | if (contentWelcome) contentWelcome.innerHTML = welcome
243 | if (contentBody) contentBody.innerHTML = body
244 | })
245 | .catch(err => {
246 | errorElem.innerHTML = int[lang].error
247 | show(errorElem)
248 | })
249 | }
250 |
251 | function adminAuth () {
252 | const password = document.getElementById('adminInput').value
253 | ubusFetch(
254 | 'session',
255 | 'login',
256 | {
257 | username: 'root',
258 | password,
259 | timeout: 5000,
260 | }
261 | )
262 | .then(res => {
263 | hide(errorElem)
264 | session = res.ubus_rpc_session
265 | document.querySelector('.admin-login').style.display = 'none'
266 | document.querySelector('#tabs').classList.remove('hidden')
267 | const adminContent = document.querySelector('.admin-content')
268 | const { backgroundColor, title, welcome, body } = content
269 | document.getElementById('adminInputTitle').value = title
270 | document.getElementById('adminInputWelcome').value = welcome
271 | document.getElementById('adminInputBody').value = body
272 | document.getElementById('adminInputBackground').value = backgroundColor
273 | listVouchers()
274 | })
275 | .catch(err => {
276 | console.log(err)
277 | show(errorElem)
278 | errorElem.innerHTML = int[lang].wrongPassword
279 | })
280 | }
281 |
282 | document.getElementById("logo-file").addEventListener("change", function (event) {
283 | compress(event)
284 | })
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/js/content.js:
--------------------------------------------------------------------------------
1 | let loader = document.createElement('div')
2 | loader.className = 'lds-ring'
3 | loader.appendChild(document.createElement('div'))
4 | loader.appendChild(document.createElement('div'))
5 | loader.appendChild(document.createElement('div'))
6 | loader.appendChild(document.createElement('div'))
7 |
8 | const show = elem => elem.classList.remove('hidden')
9 | const hide = elem => (elem.className += ' hidden')
10 |
11 | var nojsElem = document.getElementById('nojs')
12 | if (nojsElem) {
13 | nojsElem.setAttribute('value', false)
14 | }
15 |
16 | var param = '?prev='
17 | var prevUrl = window.location.search.split(param)[1]
18 | if (prevUrl) {
19 | var prevElem = document.createElement('input')
20 | prevElem.setAttribute('value', prevUrl)
21 | prevElem.setAttribute('id', 'prev')
22 | prevElem.setAttribute('name', 'prev')
23 | document.getElementById('voucher').appendChild(prevElem)
24 | }
25 |
26 | let content = {
27 | backgroundColor: 'white',
28 | title: '',
29 | welcome: '',
30 | body: '',
31 | logo: '',
32 | rules: '',
33 | mediaUrl: ''
34 | }
35 |
36 | function getContent () {
37 | ubusFetch('pirania-app', 'read_content')
38 | .then(res => {
39 | content = res
40 | const { backgroundColor, title, welcome, body, logo, rules, mediaUrl } = content
41 | document.body.style.backgroundColor = backgroundColor
42 | const contentLogo = document.getElementById('content-logo')
43 | const contentTitle = document.getElementById('content-title')
44 | const contentWelcome = document.getElementById('content-welcome')
45 | const contentBody = document.getElementById('content-body')
46 | const contentRules = document.getElementById('content-rules')
47 | const contentMedia = document.getElementById('content-media')
48 |
49 | if (contentLogo) {
50 | show(contentLogo)
51 | contentLogo.src = logo
52 | }
53 | if (contentTitle) contentTitle.innerHTML = title
54 | if (contentWelcome) contentWelcome.innerHTML = welcome
55 | if (contentBody) contentBody.innerHTML = body
56 | if (contentRules) contentRules.innerHTML = rules
57 | if (contentMedia) {
58 | var mediaType = mediaUrl.split('.')[mediaUrl.split('.').length -1]
59 | if (mediaType === 'mp4' || mediaType === 'webm' || mediaType === 'avi') {
60 | var videoContainerElem = document.createElement('video')
61 | var videoElem = document.createElement('source')
62 | contentMedia.append(videoContainerElem)
63 | videoElem.setAttribute('src', mediaUrl)
64 | videoElem.setAttribute('type', 'video/'+mediaType)
65 | videoContainerElem.appendChild(videoElem)
66 | } else if (mediaType === 'jpg' || mediaType === 'png' || mediaType === 'jpeg' || mediaType === 'gif' || mediaType === 'svg') {
67 | var imageElem = document.createElement('img')
68 | contentMedia.append(imageElem)
69 | imageElem.setAttribute('src', mediaUrl)
70 | imageElem.setAttribute('type', 'image/'+mediaType)
71 | }
72 | }
73 | })
74 | .catch(err => {
75 | document.getElementById('error').innerHTML = int[lang].error
76 | })
77 | }
78 |
79 | getContent()
80 |
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/js/int.js:
--------------------------------------------------------------------------------
1 | const userLang = navigator.language || navigator.userLanguage
2 | const lang = userLang.split('-')[0] || userLang || 'en'
3 |
4 | const int = {
5 | pt: {
6 | selectVoucher: 'Entre o voucher',
7 | createNewVoucher: 'Criar novo voucher',
8 | createManyVouchers: 'Criar muitos vouchers',
9 | changeContent: 'Mudar o conteúdo',
10 | title: 'Título',
11 | welcome: 'Bem vindo',
12 | body: 'Texto principal',
13 | backgroundColor: 'Cor de fundo',
14 | rules: 'Regras da rede',
15 | listVouchers: 'Listar vouchers',
16 | success: 'Sucesso',
17 | error: 'Erro',
18 | invalid: 'Código incorreto',
19 | wrongPassword: 'Senha incorreta',
20 | name: 'Nome',
21 | days: 'Dias',
22 | numberOfVouchers: 'Número de vouchers',
23 | authenticated: 'Seu dispositivo está autenticado',
24 | wait: 'Aguarde',
25 | continue: 'Continuar',
26 | info: 'Mais informações',
27 | },
28 | es: {
29 | selectVoucher: 'Entre el voucher',
30 | createNewVoucher: 'Crear nuevo voucher',
31 | createManyVouchers: 'Crear muchos vouchers',
32 | changeContent: 'Cambiar el contenido',
33 | title: 'Título',
34 | welcome: 'Bienvenido',
35 | body: 'Texto principal',
36 | backgroundColor: 'Color de fondo',
37 | rules: 'Reglas de la rede',
38 | listVouchers: 'Listar vouchers',
39 | success: 'Sucesso',
40 | error: 'Erro',
41 | invalid: 'Código incorrecto',
42 | wrongPassword: 'Contraseña incorrecta',
43 | name: 'Nombre',
44 | days: 'Dias',
45 | numberOfVouchers: 'Cantidad de vouchers',
46 | authenticated: 'Tu dispositivo esta autenticado',
47 | wait: 'Espere',
48 | continue: 'Seguir',
49 | info: 'Mas informaciones',
50 | },
51 | en: {
52 | selectVoucher: 'Enter a voucher',
53 | createNewVoucher: 'Create new voucher',
54 | createManyVouchers: 'Create many vouchers',
55 | changeContent: 'Change content',
56 | title: 'Title',
57 | welcome: 'Welcome text',
58 | body: 'Main text',
59 | backgroundColor: 'Background color',
60 | rules: 'Network rules',
61 | listVouchers: 'List vouchers',
62 | success: 'Success',
63 | error: 'Error',
64 | invalid: 'Invalid voucher',
65 | wrongPassword: 'Wrong password',
66 | name: 'Nome',
67 | days: 'Dias',
68 | numberOfVouchers: 'Number of vouchers',
69 | authenticated: "You're device is authenticated!",
70 | wait: 'Wait',
71 | continue: 'Continue',
72 | info: 'More information',
73 | }
74 | }
75 |
76 | Object.keys(int[lang]).map(text => {
77 | Array.from(document.getElementsByClassName(`int-${text}`)).map(
78 | element => {
79 | if (element.tagName === 'INPUT') {
80 | element.value = int[lang][text]
81 | } else {
82 | element.innerHTML = int[lang][text]
83 | }
84 | }
85 | )
86 | })
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/js/tabs.js:
--------------------------------------------------------------------------------
1 | // Start tabs.js
2 | (function() {
3 |
4 | 'use strict';
5 |
6 | /**
7 | * tabs
8 | *
9 | * @description The Tabs component.
10 | * @param {Object} options The options hash
11 | */
12 | var tabs = function(options) {
13 |
14 | var el = document.querySelector(options.el);
15 | var tabNavigationLinks = el.querySelectorAll(options.tabNavigationLinks);
16 | var tabContentContainers = el.querySelectorAll(options.tabContentContainers);
17 | var activeIndex = 0;
18 | var initCalled = false;
19 |
20 | /**
21 | * init
22 | *
23 | * @description Initializes the component by removing the no-js class from
24 | * the component, and attaching event listeners to each of the nav items.
25 | * Returns nothing.
26 | */
27 | var init = function() {
28 | if (!initCalled) {
29 | initCalled = true;
30 | el.classList.remove('no-js');
31 |
32 | for (var i = 0; i < tabNavigationLinks.length; i++) {
33 | var link = tabNavigationLinks[i];
34 | handleClick(link, i);
35 | }
36 | }
37 | };
38 |
39 | /**
40 | * handleClick
41 | *
42 | * @description Handles click event listeners on each of the links in the
43 | * tab navigation. Returns nothing.
44 | * @param {HTMLElement} link The link to listen for events on
45 | * @param {Number} index The index of that link
46 | */
47 | var handleClick = function(link, index) {
48 | link.addEventListener('click', function(e) {
49 | e.preventDefault();
50 | goToTab(index);
51 | });
52 | };
53 |
54 | /**
55 | * goToTab
56 | *
57 | * @description Goes to a specific tab based on index. Returns nothing.
58 | * @param {Number} index The index of the tab to go to
59 | */
60 | var goToTab = function(index) {
61 | if (index !== activeIndex && index >= 0 && index <= tabNavigationLinks.length) {
62 | tabNavigationLinks[activeIndex].classList.remove('is-active');
63 | tabNavigationLinks[index].classList.add('is-active');
64 | tabContentContainers[activeIndex].classList.remove('is-active');
65 | tabContentContainers[index].classList.add('is-active');
66 | activeIndex = index;
67 | }
68 | };
69 |
70 | /**
71 | * Returns init and goToTab
72 | */
73 | return {
74 | init: init,
75 | goToTab: goToTab
76 | };
77 |
78 | };
79 |
80 | /**
81 | * Attach to global namespace
82 | */
83 | window.tabs = tabs;
84 |
85 | })();
86 | // End tabs.js
87 |
88 | // Initialise at bottom of HTML
89 | var myTabs = tabs({
90 | el: '#tabs',
91 | tabNavigationLinks: '.tab-link',
92 | tabContentContainers: '.tab-content'
93 | });
94 |
95 | myTabs.init();
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/js/ubusFetch.js:
--------------------------------------------------------------------------------
1 | const url = 'http://thisnode.info/ubus'
2 | let ubusError = false
3 |
4 | function parseJSON(response) {
5 | return response.json()
6 | }
7 |
8 | const ubusFetch = (call, action, params, session) => new Promise ((resolve, reject) => {
9 | const form = {
10 | id: 99,
11 | jsonrpc: '2.0',
12 | method: 'call',
13 | params:[
14 | session || '00000000000000000000000000000000',
15 | call,
16 | action,
17 | params || {},
18 | ]
19 | }
20 | fetch(url, {
21 | method: 'POST',
22 | body: JSON.stringify(form),
23 | headers: {
24 | 'Access-Control-Allow-Origin': 'http://thisnode.info'
25 | },
26 | })
27 | .then(parseJSON)
28 | .then((res) => {
29 | if (res && res.result[1]) {
30 | resolve(res.result[1])
31 | } else {
32 | ubusError = true
33 | reject(int[lang].error)
34 | }
35 | })
36 | .catch((err) => {
37 | console.log('Ubus error ', err)
38 | ubusError = true
39 | reject(int[lang].error)
40 | })
41 | })
--------------------------------------------------------------------------------
/pirania-app/files/www/portal/js/voucher.js:
--------------------------------------------------------------------------------
1 | var validMacs = []
2 | var userIp = null
3 | var userMac = null
4 | var userIsValid = null
5 |
6 | var voucherButton = document.getElementById('voucherInput-submit')
7 | let voucherElem = document.getElementById('voucher')
8 |
9 | const validMacsForm = {
10 | id: 99,
11 | jsonrpc: '2.0',
12 | method: 'call',
13 | params: [
14 | '00000000000000000000000000000000',
15 | 'pirania',
16 | 'print_valid_macs',
17 | {}
18 | ]
19 | }
20 |
21 | const validGetClients = {
22 | id: 99,
23 | jsonrpc: '2.0',
24 | method: 'call',
25 | params: [
26 | '00000000000000000000000000000000',
27 | 'pirania-app',
28 | 'get_clients',
29 | {}
30 | ]
31 | }
32 |
33 | async function loadAsyncData () {
34 | await getIp()
35 | await getValidClients()
36 | await getValidMacs()
37 | }
38 |
39 | function init () {
40 | console.log('Welcome to Pirania!')
41 | // Add responses
42 | var error = document.createElement('h4')
43 | var result = document.createElement('p')
44 | var form = document.getElementsByClassName('voucher')[0]
45 | form.append(error)
46 | form.append(result)
47 | error.setAttribute('id', 'error')
48 | result.setAttribute('id', 'result')
49 | error.className = 'hidden'
50 | result.className = 'hidden'
51 |
52 | // Add list
53 | var deviceList = document.createElement('button')
54 | var add = document.createElement('span')
55 | var icon = document.createElement('div')
56 | document.body.appendChild(deviceList)
57 | deviceList.appendChild(add)
58 | deviceList.appendChild(icon)
59 | icon.className = 'mobile icon'
60 | add.innerHTML = '+'
61 | deviceList.setAttribute('id', 'other-devices')
62 | deviceList.addEventListener('click', async function (e) {
63 | e.preventDefault()
64 | showingList = !showingList
65 | if (showingList) {
66 | show(stationList)
67 | show(voucherButton)
68 | show(voucherElem)
69 | deviceList.style.backgroundColor = '#A593E0'
70 | } else {
71 | hide(stationList)
72 | deviceList.style.backgroundColor = ''
73 | }
74 | await loadAsyncData()
75 | })
76 | }
77 |
78 | init()
79 |
80 | var showingList = false
81 | var stationList = document.getElementById('station-list')
82 | var errorElem = document.getElementById('error')
83 | var resultElem = document.getElementById('result')
84 |
85 | function prepareResult (res) {
86 | if (res.error) {
87 | console.log(res.error)
88 | errorElem.innerHTML = res.error
89 | show(errorElem)
90 | ubusError = true
91 | } else if (res && res.result[1]) return res.result[1]
92 | else return false
93 | }
94 |
95 | function authVoucher () {
96 | if (!userMac) return
97 | let mac
98 | if (showingList) {
99 | mac = document.getElementById('stations').value || userMac
100 | } else {
101 | mac = userMac
102 | }
103 | let voucher = voucherElem.value.toLowerCase()
104 | voucherElem.after(loader)
105 | show(loader)
106 | const authVoucherForm = {
107 | id: 99,
108 | jsonrpc: '2.0',
109 | method: 'call',
110 | params: [
111 | '00000000000000000000000000000000',
112 | 'pirania',
113 | 'auth_voucher',
114 | {
115 | voucher,
116 | mac
117 | }
118 | ]
119 | }
120 | fetch(url, {
121 | method: 'POST',
122 | body: JSON.stringify(authVoucherForm),
123 | headers: {
124 | 'Access-Control-Allow-Origin': 'http://thisnode.info'
125 | }
126 | })
127 | .then(parseJSON)
128 | .then(prepareResult)
129 | .then(res => {
130 | hide(loader)
131 | show(voucherButton)
132 | if (res && res.success) {
133 | result.innerHTML = int[lang].success
134 | show(result)
135 | hide(errorElem)
136 | loadAsyncData()
137 | } else if (res && !res.success) {
138 | errorElem.innerHTML = int[lang].invalid
139 | show(errorElem)
140 | }
141 | voucherElem.value = ''
142 | })
143 | .catch(err => {
144 | console.log('UBUS error:', err)
145 | errorElem.innerHTML = err
146 | show(errorElem)
147 | ubusError = true
148 | })
149 | }
150 |
151 | function getIp () {
152 | return fetch('/cgi-bin/pirania/client_ip', {
153 | headers: {
154 | 'Access-Control-Allow-Origin': 'http://thisnode.info'
155 | }
156 | })
157 | .then(async i => {
158 | const res = await i.json()
159 | userIp = res.ip
160 | userMac = res.mac
161 | userIsValid = res.valid
162 | })
163 | .catch(err => {
164 | console.log('Error fetching mac:', err)
165 | ubusError = true
166 | })
167 | }
168 |
169 | function getValidClients () {
170 | if (!ubusError) {
171 | const myDiv = document.getElementById('station-list')
172 | const exists = document.getElementById('stations')
173 | if (!exists) {
174 | const select = document.createElement('select')
175 | select.id = 'stations'
176 | myDiv.appendChild(select)
177 | }
178 | }
179 | return fetch(url, {
180 | method: 'POST',
181 | body: JSON.stringify(validGetClients),
182 | headers: {
183 | 'Access-Control-Allow-Origin': 'http://thisnode.info'
184 | }
185 | })
186 | .then(parseJSON)
187 | .then(prepareResult)
188 | .then(res => {
189 | if (res && !ubusError) {
190 | document.getElementById('stations').innerHTML = ''
191 | res.clients.map(i => {
192 | const valid = validMacs.filter(valid => i.mac === valid).length > 0
193 | const node = document.createElement('option')
194 | let textnode = document.createTextNode('')
195 | if (userIp === i.ip) {
196 | userMac = i.mac
197 | node.selected = true
198 | }
199 | const isIp = userIp === i.ip ? '📱 ' : ''
200 | textnode.nodeValue = valid
201 | ? isIp + i.station + ' ✅'
202 | : isIp + i.station
203 | node.value = i.mac
204 | node.appendChild(textnode)
205 | return document.getElementById('stations').appendChild(node)
206 | })
207 | }
208 | })
209 | .catch(err => {
210 | console.log(int[lang].error, err)
211 | errorElem.innerHTML = int[lang].error
212 | show(errorElem)
213 | ubusError = true
214 | })
215 | }
216 |
217 | function getValidMacs () {
218 | fetch(url, {
219 | method: 'POST',
220 | body: JSON.stringify(validMacsForm),
221 | headers: {
222 | 'Access-Control-Allow-Origin': 'http://thisnode.info'
223 | }
224 | })
225 | .then(parseJSON)
226 | .then(res => {
227 | if (res && res.result[1]) {
228 | validMacs = res.result[1].macs
229 | if (validMacs.length > 0) {
230 | getValidClients()
231 | }
232 | } else if (res.error) {
233 | console.log(res.error)
234 | errorElem.innerHTML = int[lang].error
235 | ubusError = true
236 | }
237 | })
238 | .catch(err => {
239 | console.log(int[lang].error, err)
240 | errorElem.innerHTML = int[lang].error
241 | ubusError = true
242 | })
243 | }
244 |
245 | voucherButton.addEventListener('click', function (e) {
246 | hide(voucherButton)
247 | show(loader)
248 | if (showingList) {
249 | e.preventDefault()
250 | authVoucher()
251 | }
252 | })
253 |
--------------------------------------------------------------------------------
/pirania/files/etc/config/pirania:
--------------------------------------------------------------------------------
1 | config base_config 'base_config'
2 | option enabled '0'
3 | option portal_url 'http://thisnode.info/portal/'
4 | option url_auth '/portal/auth.html'
5 | option url_authenticated '/portal/authenticated.html'
6 | option url_info '/portal/info.html'
7 | option url_fail '/portal/fail.html'
8 | option db_path '/etc/pirania/db.csv'
9 | option elections_path '/etc/pirania/elections.json'
10 | option hooks_path '/etc/pirania/hooks/'
11 | option uploadlimit '10000000'
12 | option downloadlimit '50000'
13 | option append_ipt_rules '0' # if set to 1, iptables rules will be Appended instead of Inserted
14 | list whitelist_ipv4 '10.0.0.0/8'
15 | list whitelist_ipv4 '172.16.0.0/12'
16 | list whitelist_ipv4 '192.168.0.0/16'
17 | list whitelist_ipv6 '2a00:1508:0a00::/40'
18 | list catch_interfaces 'br-lan'
19 | list catch_interfaces 'anygw'
20 |
--------------------------------------------------------------------------------
/pirania/files/etc/init.d/pirania:
--------------------------------------------------------------------------------
1 | #!/bin/sh /etc/rc.common
2 |
3 | START=99
4 | NAME=pirania
5 | PROG=/usr/bin/captive-portal
6 |
7 | start() {
8 | if [ $(uci get pirania.base_config.enabled) == 1 ]; then
9 | logger -t pirania "Running portal captive"
10 | $PROG
11 | /usr/lib/lua/voucher/hooks.lua start
12 | else
13 | logger -t pirania "Portal captive is disabled"
14 | fi
15 | }
16 |
17 | stop () {
18 | /usr/lib/lua/voucher/hooks.lua stop
19 | $PROG stop
20 | logger -t pirania "Portal captive stopped"
21 | }
22 |
--------------------------------------------------------------------------------
/pirania/files/etc/init.d/pirania-dnsmasq:
--------------------------------------------------------------------------------
1 | #!/bin/sh /etc/rc.common
2 |
3 | START=50
4 |
5 | USE_PROCD=1
6 |
7 | DNSMASQ_BIN="/usr/sbin/dnsmasq"
8 |
9 | start_service() {
10 | procd_open_instance
11 | procd_set_param command $DNSMASQ_BIN \
12 | --keep-in-foreground \
13 | --port=59053 \
14 | --no-resolv \
15 | --server=/$(uci get dhcp.@dnsmasq[0].domain)/$(uci get network.lm_net_br_lan_anygw_if.ipaddr) \
16 | --addn-hosts=/var/hosts/shared-state-dnsmasq_hosts \
17 | --address=/thisnode.info/$(uci get network.lm_net_br_lan_anygw_if.ipaddr) \
18 | --address=/\#/1.2.3.4
19 |
20 | # respawn automatically if something died, be careful if you have an alternative process supervisor
21 | # if process dies sooner than respawn_threshold, it is considered crashed and after 5 retries the service is stopped
22 | procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
23 |
24 | procd_set_param stderr 1 # forward stderr of the command to logd
25 | procd_close_instance
26 | }
--------------------------------------------------------------------------------
/pirania/files/etc/init.d/pirania-uhttpd:
--------------------------------------------------------------------------------
1 | #!/bin/sh /etc/rc.common
2 |
3 | START=50
4 |
5 | USE_PROCD=1
6 |
7 | UHTTPD_BIN="/usr/sbin/uhttpd"
8 |
9 | start_service() {
10 | procd_open_instance
11 | procd_set_param command $UHTTPD_BIN -f -h /www/pirania-redirect/ -E / -l / -L /www/pirania-redirect/redirect -n 20 -p 59080
12 | # respawn automatically if something died, be careful if you have an alternative process supervisor
13 | # if process dies sooner than respawn_threshold, it is considered crashed and after 5 retries the service is stopped
14 | procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
15 |
16 | procd_set_param stderr 1 # forward stderr of the command to logd
17 | procd_close_instance
18 | }
19 |
--------------------------------------------------------------------------------
/pirania/files/etc/pirania/db.csv:
--------------------------------------------------------------------------------
1 | key,voucher,expiretime,uploadlimit,downloadlimit,amountofmacsallowed,usedmacs,
2 |
--------------------------------------------------------------------------------
/pirania/files/usr/bin/captive-portal:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # requires ip6tables-mod-nat and ipset
3 |
4 | clean_tables () {
5 | echo "Cleaning captive-portal rules"
6 | for ipvX in ipv4 ipv6 ; do
7 | if [ "$ipvX" = "ipv4" ] ; then
8 | iptables=iptables
9 | family=inet
10 | ipaddr=ipaddr
11 | else
12 | iptables=ip6tables
13 | family=inet6
14 | ipaddr=ip6addr
15 | fi
16 |
17 | ### Cleanup
18 | for interface in br-lan anygw; do
19 | $iptables -t mangle -D PREROUTING -i $interface -j pirania
20 | done
21 |
22 | $iptables -t nat -D PREROUTING -j pirania
23 | $iptables -t filter -D FORWARD -j pirania
24 |
25 | for table in mangle nat filter; do
26 | $iptables -t $table -F pirania
27 | $iptables -t $table -X pirania
28 | done
29 | done
30 | }
31 |
32 | clean_sets () {
33 | ipset flush pirania-auth-macs
34 | for ipvX in ipv4 ipv6 ; do
35 | ipset flush pirania-whitelist-$ipvX
36 | done
37 | }
38 |
39 | set_iptables () {
40 | echo "Apply captive-portal rules"
41 |
42 | append_ipt_rules=$(uci get pirania.base_config.append_ipt_rules 2> /dev/null)
43 | if [ "$append_ipt_rules" = "1" ] ; then
44 | AorI="A"
45 | else
46 | AorI="I"
47 | fi
48 |
49 | for ipvX in ipv4 ipv6 ; do
50 | if [ "$ipvX" = "ipv4" ] ; then
51 | iptables=iptables
52 | family=inet
53 | ipaddr=ipaddr
54 | anygw=$(uci get network.lm_net_br_lan_anygw_if.ipaddr)
55 | else
56 | iptables=ip6tables
57 | family=inet6
58 | ipaddr=ip6addr
59 | anygw=[$(uci get network.lan.ip6addr | cut -d/ -f1)]
60 | fi
61 |
62 | ### Buildup
63 | for table in mangle nat filter; do
64 | $iptables -t $table -N pirania
65 | done
66 |
67 | $iptables -t nat -$AorI PREROUTING -j pirania
68 | $iptables -t filter -$AorI FORWARD -j pirania
69 |
70 | for interface in $(uci get pirania.base_config.catch_interfaces); do
71 | $iptables -t mangle -$AorI PREROUTING -i $interface -j pirania
72 | done
73 |
74 | $iptables -t nat -A pirania -p udp -m set ! --match-set pirania-whitelist-$ipvX src -m set ! --match-set pirania-auth-macs src --dport 53 -j DNAT --to-destination $anygw:59053
75 |
76 | $iptables -t mangle -A pirania -m set --match-set pirania-auth-macs src -j RETURN
77 | $iptables -t mangle -A pirania -m set --match-set pirania-whitelist-$ipvX dst -j RETURN
78 | $iptables -t mangle -A pirania -j MARK --set-mark 0x66/0xff # everything not auth nor whitelisted will be marked for REJECT
79 | $iptables -t mangle -A pirania -p tcp -m tcp --dport 80 -j MARK --set-mark 0x80/0xff # unless is dport 80, re-set mark for REDIRECT
80 |
81 | $iptables -t nat -A pirania -p tcp -m tcp -m mark --mark 0x80/0xff -j REDIRECT --to-ports 59080
82 | $iptables -t filter -A pirania -p tcp -m mark --mark 0x66/0xff -j REJECT --reject-with tcp-reset
83 | $iptables -t filter -A pirania -m mark --mark 0x66/0xff -j REJECT
84 | done
85 | }
86 |
87 | set_ipsets () {
88 | ipset -exist create pirania-auth-macs hash:mac timeout 0
89 | for mac in $(voucher print_valid_macs) ; do
90 | ipset -exist add pirania-auth-macs $mac
91 | done
92 | for ipvX in ipv4 ipv6 ; do
93 | if [ "$ipvX" = "ipv4" ] ; then
94 | family=inet
95 | else
96 | family=inet6
97 | fi
98 | ipset -exist create pirania-whitelist-$ipvX hash:net family $family
99 | for item in $(uci get pirania.base_config.whitelist_$ipvX); do
100 | ipset -exist add pirania-whitelist-$ipvX $item
101 | done
102 | done
103 | }
104 |
105 | # check if captive-portal is enabled in /etc/config/pirania
106 | enabled=$(uci get pirania.base_config.enabled)
107 |
108 | if [ "$1" = "start" ]; then
109 | echo "Running captive-portal"
110 | clean_tables
111 | clean_sets
112 | set_ipsets
113 | set_iptables
114 | exit
115 | elif [ "$1" = "update" ] ; then
116 | clean_sets
117 | set_ipsets
118 | exit
119 | elif [ "$1" = "clean" ] || [ "$1" = "stop" ] ; then
120 | clean_tables
121 | clean_sets
122 | exit
123 | elif [ "$enabled" = "1" ]; then
124 | clean_tables
125 | clean_sets
126 | set_ipsets
127 | set_iptables
128 | exit
129 | else
130 | echo "Pirania captive-portal is disabled. Try running captive-portal start"
131 | exit
132 | fi
133 |
134 |
--------------------------------------------------------------------------------
/pirania/files/usr/bin/voucher:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local config = require('voucher.config')
4 | local dba = require('voucher.db')
5 | local logic = require('voucher.logic')
6 | local ft = require('voucher.functools')
7 | local utils = require('voucher.utils')
8 | local uci = require('uci')
9 | local json = require 'luci.json'
10 |
11 | local uci_cursor = uci.cursor()
12 | local arguments
13 | local action
14 | local context
15 |
16 | captive_portal = {}
17 |
18 | local function printJson (obj)
19 | print(json.encode(obj))
20 | end
21 |
22 | local function shell(command)
23 | local handle = io.popen(command)
24 | local result = handle:read("*a")
25 | handle:close()
26 | return result
27 | end
28 |
29 | local function split(inputstr, sep)
30 | if sep == nil then
31 | sep = "+"
32 | end
33 | local t={}
34 | for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
35 | table.insert(t, str)
36 | end
37 | return t
38 | end
39 |
40 | -- Show or change portal url
41 | captive_portal.url = function(context)
42 | local url = ''
43 | if (context[1] == 'show') then
44 | url = uci_cursor:get("pirania", "base_config", "portal_url")
45 | else
46 | url = context[1] and context[1] or "http://thisnode.info/portal"
47 | uci_cursor:set("pirania", "base_config", "portal_url", url)
48 | uci_cursor:commit("pirania")
49 | end
50 | print(url)
51 | end
52 |
53 | -- Update voucher date
54 | local function updateVoucher(name, date)
55 | local result = {}
56 | local db = dba.load(config.db)
57 | local toRenew = {}
58 | for _, voucher in pairs (db.data) do
59 | if (voucher[1] == name) then
60 | toRenew = voucher
61 | end
62 | end
63 | if (#toRenew > 0) then
64 | result.success = true
65 | toRenew[3] = tonumber(date)
66 | local validVouchers = ft.filter(function(row, index) return row[1] ~= name end, db.data)
67 | local newDb = db
68 | newDb.data = validVouchers
69 | table.insert(newDb.data, toRenew)
70 | dba.save(config.db, newDb)
71 | else result.success = false
72 | end
73 | return result
74 | end
75 |
76 |
77 | --[[
78 | --Checks if a context(defined by a mac and a voucher code)
79 | --is authorized to be used, and associates the mac to the
80 | --voucher code if needed.
81 | --]]
82 | captive_portal.auth_voucher = function(context)
83 | local mac = context[1]
84 | local voucher = context[2]
85 |
86 | local db = dba.load(config.db)
87 | local res = logic.auth_voucher(db, mac, voucher)
88 | print ( res.limit[1], res.limit[2], res.limit[3], res.limit[4] )
89 | if (res.success == true) then
90 | print('Saving to DB')
91 | print('...')
92 | dba.save(config.db, db)
93 | print('saved!')
94 | end
95 | end
96 |
97 | --[[
98 | --Checks if the mac of the given context is allowed to browse.
99 | --]]
100 | captive_portal.status = function(context)
101 | local mac = context[1]
102 |
103 | local db = dba.load(config.db)
104 | print ( logic.status(db, mac) )
105 | end
106 |
107 | -- List all
108 | captive_portal.list_vouchers = function()
109 | local result = {}
110 | local vName = 1
111 | local vSecret = 2
112 | local vExpire = 3
113 | local vMacsAllowed = 6
114 | local usedMacs = 7
115 | local db = dba.load(config.db)
116 | for _, v in pairs(db.data) do
117 | result[_] = {}
118 | result[_].name = v[vName]
119 | result[_].expires = v[vExpire]
120 | result[_].voucher = v[vSecret]
121 | result[_].macsAllowed = v[vMacsAllowed]
122 | result[_].macs = split(v[usedMacs])
123 | end
124 | printJson(result)
125 | end
126 |
127 | -- Renew voucher
128 | captive_portal.renew_voucher = function(context)
129 | local voucherName = context[1]
130 | local renewDate = context[2]
131 | printJson(updateVoucher(voucherName, renewDate))
132 | end
133 |
134 | -- Remove voucher
135 | captive_portal.remove_voucher = function(context)
136 | local voucherName = context[1]
137 | printJson(updateVoucher(voucherName, 0))
138 | end
139 |
140 | --[[
141 | --Adds a voucher to the db with the params defined by context.
142 | --]]
143 | captive_portal.add_voucher = function(context)
144 | local key = context[1]
145 | local voucher = context[2]
146 | local epoc = context[3]
147 | local upload = context[4] and context[4] or '0'
148 | local download = context[5] and context[5] or '0'
149 | local amountofmacsallowed = context[6] and context[6] or '0'
150 | local exists = false
151 |
152 | local db = dba.load(config.db)
153 | for _, v in pairs (db.data) do
154 | if (v[1] == key) then
155 | exists = true
156 | end
157 | end
158 | if exists == false then
159 | local retval = { logic.add_voucher(db, key, voucher, epoc, upload, download, amountofmacsallowed)}
160 | dba.save(config.db, db)
161 | print ( unpack(retval) )
162 | else
163 | print(0)
164 | end
165 | end
166 |
167 | captive_portal.add_many_vouchers = function(context, data)
168 | local result = {}
169 | local table = utils.split(data, "\n")
170 | local db = dba.load(config.db)
171 | local error = false
172 | for _,val in pairs (table) do
173 | local jdata = json.decode(val)
174 | local upload = jdata.upload and jdata.upload or '10'
175 | local download = jdata.download and jdata.download or '10'
176 | local amountofmacsallowed = jdata.amountofmacsallowed and jdata.amountofmacsallowed or '1'
177 | local res = logic.add_voucher(db, jdata.key, jdata.voucher, jdata.epoc, upload, download, amountofmacsallowed)
178 | if (res == nil) then error = true end
179 | end
180 | result.success = not error
181 | dba.save(config.db, db)
182 | printJson(result)
183 | end
184 |
185 | -- TODO refactor eliminate bash portion awk sed bash-isms
186 | captive_portal.print_valid_macs = function()
187 | local db = dba.load(config.db)
188 | local macs = logic.valid_macs(db)
189 | for _, mac in ipairs(macs) do
190 | print ( mac )
191 | end
192 | end
193 |
194 | -- if is main
195 | if debug.getinfo(2).name == nil then
196 | arguments = { ... }
197 | action = arguments[1]
198 | context = ft.filter(function(row, index) return index > 1 end, arguments)
199 | if (action == 'add_many_vouchers') then stdin = io.stdin:read("*all") end
200 | captive_portal[action](context, stdin)
201 | end
202 |
203 | return captive_portal
204 |
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/config.lua:
--------------------------------------------------------------------------------
1 | local uci = require("uci")
2 | local pirania_config = 'pirania'
3 |
4 | ucicursor = uci.cursor()
5 |
6 | config = {
7 | db = ucicursor:get(pirania_config, 'base_config', 'db_path'),
8 | uploadlimit = ucicursor:get(pirania_config, 'base_config', 'uploadlimit'),
9 | downloadlimit = ucicursor:get(pirania_config, 'base_config', 'downloadlimit'),
10 | hooksDir = ucicursor:get(pirania_config, 'base_config', 'hooks_path')
11 | }
12 |
13 | return config
14 |
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/db.lua:
--------------------------------------------------------------------------------
1 | #!/bin/lua
2 |
3 | local utils = require('voucher.utils')
4 | local ft = require('voucher.functools')
5 | local hooks = require('voucher.hooks')
6 |
7 | dba = {}
8 |
9 | local function read_db_from_csv(dbinfo)
10 | local rawtable = utils.from_csv_to_table(dbinfo);
11 | if rawtable == nil then
12 | fho,err = io.open(dbinfo, "w")
13 | fho:write('key,voucher,expiretime,uploadlimit,downloadlimit,amountofmacsallowed,usedmacs,')
14 | rawtable = utils.from_csv_to_table(dbinfo);
15 | end
16 | local table = {
17 | headers = rawtable[1],
18 | data = ft.filter(function(row, index) return index > 1 end, rawtable)
19 | }
20 | return table
21 | end
22 |
23 | local function write_db_to_csv(csvname, db)
24 | data = {}
25 | data[1] = db.headers
26 | local idx = 2
27 |
28 | for _, v in pairs(db.data) do
29 | data[idx] = v
30 | idx = idx + 1
31 | end
32 |
33 | utils.from_table_to_csv(csvname, data)
34 | hooks("db_change")
35 | end
36 |
37 | function dba.get_vouchers_by_voucher(db, voucherid)
38 | local voucher_column = functools.search(function(val) return val == 'voucher' end, db.headers)
39 | return ft.filter(function(voucher) return voucher[voucher_column] == voucherid end, db.data)
40 | end
41 |
42 | function dba.get_vouchers_by_mac(db, mac)
43 | local mac_column = functools.search(function(val) return val == 'usedmacs' end, db.headers)
44 | return ft.filter(function(maclist)
45 | if(maclist[mac_column]) then
46 | local search_result = string.find(maclist[mac_column], mac)
47 | return search_result ~= nil
48 | end
49 | return false
50 | end, db.data)
51 | end
52 |
53 | function dba.get_all_vouchers(db)
54 | return db.data
55 | end
56 |
57 | function dba.describe_values(db, row)
58 | local described_row = {}
59 | for i, v in pairs(db.headers) do
60 | described_row[v] = row[i]
61 | end
62 |
63 | return described_row
64 | end
65 |
66 | function dba.add_voucher(db, key, voucher, epoc, upload, download, amountofmacsallowed)
67 | local data = {key, voucher, epoc, upload, download, amountofmacsallowed, ''}
68 | table.insert(db.data, data)
69 | return data
70 | end
71 |
72 | dba.load = read_db_from_csv
73 | dba.save = write_db_to_csv
74 |
75 | return dba
76 |
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/debugtools.lua:
--------------------------------------------------------------------------------
1 | #!/bin/lua
2 |
3 | debugtools = {}
4 |
5 | debugtools.print_r = function ( t )
6 | local print_r_cache={}
7 | local function sub_print_r(t,indent)
8 | if (print_r_cache[tostring(t)]) then
9 | print(indent.."*"..tostring(t))
10 | else
11 | print_r_cache[tostring(t)]=true
12 | if (type(t)=="table") then
13 | for pos,val in pairs(t) do
14 | if (type(val)=="table") then
15 | print(indent.."["..pos.."] => "..tostring(t).." {")
16 | sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
17 | print(indent..string.rep(" ",string.len(pos)+6).."}")
18 | elseif (type(val)=="string") then
19 | print(indent.."["..pos..'] => "'..val..'"')
20 | else
21 | print(indent.."["..pos.."] => "..tostring(val))
22 | end
23 | end
24 | else
25 | print(indent..tostring(t))
26 | end
27 | end
28 | end
29 | if (type(t)=="table") then
30 | print(tostring(t).." {")
31 | sub_print_r(t," ")
32 | print("}")
33 | else
34 | sub_print_r(t," ")
35 | end
36 | print()
37 | end
38 |
39 | return debugtools
40 |
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/functools.lua:
--------------------------------------------------------------------------------
1 | functools = {}
2 |
3 | -- Lua implementation of the curry function
4 | -- Developed by tinylittlelife.org
5 | -- released under the WTFPL (http://sam.zoy.org/wtfpl/)
6 |
7 | -- curry(func, num_args) : take a function requiring a tuple for num_args arguments
8 | -- and turn it into a series of 1-argument functions
9 | -- e.g.: you have a function dosomething(a, b, c)
10 | -- curried_dosomething = curry(dosomething, 3) -- we want to curry 3 arguments
11 | -- curried_dosomething (a1) (b1) (c1) -- returns the result of dosomething(a1, b1, c1)
12 | -- partial_dosomething1 = curried_dosomething (a_value) -- returns a function
13 | -- partial_dosomething2 = partial_dosomething1 (b_value) -- returns a function
14 | -- partial_dosomething2 (c_value) -- returns the result of dosomething(a_value, b_value, c_value)
15 | function functools.curry(func, num_args)
16 |
17 | -- currying 2-argument functions seems to be the most popular application
18 | num_args = num_args or 2
19 |
20 | -- helper
21 | local function curry_h(argtrace, n)
22 | if 0 == n then
23 | -- reverse argument list and call function
24 | return func(functools.reverse(argtrace()))
25 | else
26 | -- "push" argument (by building a wrapper function) and decrement n
27 | return function (onearg)
28 | return curry_h(function () return onearg, argtrace() end, n - 1)
29 | end
30 | end
31 | end
32 |
33 | -- no sense currying for 1 arg or less
34 | if num_args > 1 then
35 | return curry_h(function () return end, num_args)
36 | else
37 | return func
38 | end
39 | end
40 |
41 | -- reverse(...) : take some tuple and return a tuple of elements in reverse order
42 | --
43 | -- e.g. "reverse(1,2,3)" returns 3,2,1
44 | function functools.reverse(...)
45 |
46 | --reverse args by building a function to do it, similar to the unpack() example
47 | local function reverse_h(acc, v, ...)
48 | if 0 == select('#', ...) then
49 | return v, acc()
50 | else
51 | return reverse_h(function () return v, acc() end, ...)
52 | end
53 | end
54 |
55 | -- initial acc is the end of the list
56 | return reverse_h(function () return end, ...)
57 | end
58 |
59 |
60 | function functools.map(func, tbl)
61 | local newtbl = {}
62 | for i,v in pairs(tbl) do
63 | newtbl[i] = func(v)
64 | end
65 | return newtbl
66 | end
67 |
68 | function functools.filter(func, tbl)
69 | local newtbl= {}
70 | local index=1;
71 | for i,v in pairs(tbl) do
72 | if func(v, i) then
73 | newtbl[index]=v
74 | index = index + 1
75 | end
76 | end
77 | return newtbl
78 | end
79 |
80 | function functools.search(func, tbl)
81 | for i,v in pairs(tbl) do
82 | if func(v, i) then
83 | return i
84 | end
85 | end
86 |
87 | return 0
88 | end
89 |
90 | return functools
91 |
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/hooks.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local config = require('voucher.config')
4 | local fs = require("nixio.fs")
5 |
6 | local hooks = function(action)
7 | local hookPath = config.hooksDir..action..'/'
8 | local files = fs.dir(hookPath) or pairs({})
9 | for file in files do
10 | os.execute(hookPath..file)
11 | end
12 |
13 | end
14 |
15 | if debug.getinfo(2).name == nil then
16 | arguments = { ... }
17 | if (arguments ~= nil and arguments[1] ~= nil) then
18 | hooks(arguments[1])
19 | end
20 | end
21 |
22 | return hooks
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/logic.lua:
--------------------------------------------------------------------------------
1 | #!/bin/lua
2 |
3 | local dba = require('voucher.db')
4 | local config = require('voucher.config')
5 |
6 | logic = {}
7 |
8 | local function shell(command)
9 | local handle = io.popen(command)
10 | local result = handle:read("*a")
11 | handle:close()
12 | return result
13 | end
14 |
15 | local function isMac(string)
16 | local string = string:match("%w%w:%w%w:%w%w:%w%w:%w%w:%w%w")
17 | if string then
18 | return true
19 | else
20 | return false
21 | end
22 | end
23 |
24 |
25 | local function dateNow()
26 | local output = shell('date +%s000')
27 | local parsed = string.gsub(output, "%s+", "")
28 | local dateNow = tonumber(parsed)
29 | return dateNow
30 | end
31 |
32 |
33 |
34 | local function use_voucher(db, voucher, mac)
35 | macs = voucher[7]
36 |
37 | if (string.find(macs, mac) == nil) then
38 | if (macs == '') then
39 | voucher[7] = mac
40 | else
41 | voucher[7] = macs .. '+' .. mac
42 | end
43 | end
44 | end
45 |
46 | local function get_valid_rawvoucher(db, rawvouchers)
47 | local voucher, expiretime, uploadlimit, downloadlimit
48 |
49 | for _, rawvoucher in ipairs( rawvouchers ) do
50 | if (logic.check_valid_voucher(db, rawvoucher)) then
51 | return rawvoucher
52 | end
53 | end
54 |
55 | return
56 | end
57 |
58 | local function get_limit_from_rawvoucher(db, rawvoucher)
59 | local voucher, expiretime, uploadlimit, downloadlimit
60 |
61 | if (rawvoucher ~= nil) then
62 | voucher = dba.describe_values (db, rawvoucher)
63 |
64 | if tonumber(voucher.expiretime) ~= nil then
65 | expiretime = tostring( tonumber( voucher.expiretime ) - dateNow())
66 | uploadlimit = voucher.uploadlimit ~= '0' and voucher.uploadlimit or config.uploadlimit
67 | downloadlimit = voucher.downloadlimit ~= '0' and voucher.downloadlimit or config.downloadlimit
68 | currentmacs = table.getn(utils.string_split(voucher.usedmacs, '+'))
69 | valid = currentmacs < tonumber(voucher.amountofmacsallowed) and '1' or '0'
70 | return expiretime, uploadlimit, downloadlimit, valid
71 | end
72 | end
73 |
74 | return '0', '0', '0', '0'
75 | end
76 |
77 | local function checkIfIpv4(ip)
78 | if ip == nil or type(ip) ~= "string" then
79 | return 0
80 | end
81 | -- check for format 1.11.111.111 for ipv4
82 | local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
83 | if (#chunks == 4) then
84 | for _,v in pairs(chunks) do
85 | if (tonumber(v) < 0 or tonumber(v) > 255) then
86 | return 0
87 | end
88 | end
89 | return true
90 | else
91 | return false
92 | end
93 | end
94 |
95 | function logic.getIpv4AndMac()
96 | local address = os.getenv('REMOTE_ADDR')
97 | local isIpv4 = checkIfIpv4(address)
98 | if (isIpv4) then
99 | local ipv4macCommand = "cat /proc/net/arp | grep "..address.." | awk -F ' ' '{print $4}' | head -n 1"
100 | fd = io.popen(ipv4macCommand, 'r')
101 | ipv4mac = fd:read('*l')
102 | fd:close()
103 | local res = {}
104 | res.ip = address
105 | res.mac = ipv4mac
106 | return res
107 | else
108 | local ipv6macCommand = "ip neighbor | grep "..address.." | awk -F ' ' '{print $5}' | head -n 1"
109 | fd6 = io.popen(ipv6macCommand, 'r')
110 | ipv6mac = fd6:read('*l')
111 | fd6:close()
112 | local ipv4Command = "cat /proc/net/arp | grep "..ipv6mac.." | awk -F ' ' '{print $1}' | head -n 1"
113 | fd4 = io.popen(ipv4Command, 'r')
114 | ipv4 = fd4:read('*l')
115 | fd4:close()
116 | local res = {}
117 | res.ip = ipv4
118 | res.mac = ipv6mac
119 | return res
120 | end
121 | end
122 |
123 | function logic.check_valid_voucher(db, row)
124 | local expireDate = tonumber(dba.describe_values(db, row).expiretime) or 0
125 | if (expireDate ~= nil) then
126 | return expireDate > dateNow()
127 | end
128 | end
129 |
130 | function logic.check_voucher_validity(voucherid, db)
131 | local res = {}
132 | res.valid = false
133 | local rawvouchers = dba.get_vouchers_by_voucher(db, voucherid)
134 | if (rawvouchers ~= nil) then
135 | local voucher = get_valid_rawvoucher(db, rawvouchers)
136 | local expiretime, uploadlimit, downloadlimit, valid = get_limit_from_rawvoucher(db, voucher)
137 | if(voucher ~= nil and valid == '1' and tonumber(expiretime) > 0) then
138 | res.valid = true
139 | res.voucher = voucher
140 | end
141 | end
142 | return res
143 | end
144 |
145 | local function setIpset(mac, expiretime)
146 | -- ipset only supports timeout up to 4294967
147 | if tonumber(expiretime) > 4294967 then expiretime = 4294967 end
148 | os.execute("ipset -exist add pirania-auth-macs " .. mac .. " timeout ".. expiretime)
149 | end
150 |
151 | function logic.auth_voucher(db, mac, voucherid)
152 | local response = {
153 | success=false,
154 | limit={'0', '0', '0', '0'}
155 | }
156 | local res = logic.check_voucher_validity(voucherid, db)
157 | if (res.valid) then
158 | use_voucher(db, res.voucher, mac)
159 | setIpset(mac, res.voucher[3])
160 | response.limit={ res.voucher[3], res.voucher[4], res.voucher[5], res.voucher[6] }
161 | response.success=true
162 | end
163 | return response
164 | end
165 |
166 | function logic.check_mac_validity(mac)
167 | local command = 'voucher print_valid_macs | grep -o '..mac..' | wc -l | grep "[^[:blank:]]"'
168 | fd = io.popen(command, 'r')
169 | local output = fd:read('*all')
170 | fd:close()
171 | return tonumber(output)
172 | end
173 |
174 | function logic.add_voucher(db, key, voucher, epoc, upload, download, amountofmacsallowed)
175 | local rawvoucher = dba.add_voucher(db, key, voucher, epoc, upload, download, amountofmacsallowed)
176 |
177 | return get_limit_from_rawvoucher(db, rawvoucher)
178 | end
179 |
180 | function logic.valid_macs(db)
181 | local rawvouchers, rawvoucher, macs, currentmacs
182 | macs = {}
183 | rawvouchers = dba.get_all_vouchers(db)
184 |
185 | for _, rawvoucher in ipairs( rawvouchers ) do
186 | if logic.check_valid_voucher(db, rawvoucher) then
187 | local voucher = dba.describe_values(db, rawvoucher)
188 | currentmacs = utils.string_split(voucher.usedmacs, '+')
189 | for _, mac in ipairs( currentmacs ) do
190 | local intAmount = tonumber(voucher.amountofmacsallowed)
191 | local amountofmacs = 0
192 | if (intAmount ~= nil) then
193 | amountofmacs = intAmount
194 | end
195 | if (_ <= amountofmacs and isMac(mac)) then
196 | table.insert(macs, mac)
197 | end
198 | end
199 | end
200 | end
201 |
202 | return macs
203 | end
204 |
205 | function logic.status(db, mac)
206 | local rawvouchers, rawvoucher
207 | rawvouchers = dba.get_vouchers_by_mac(db, mac)
208 |
209 | for _, rawvoucher in ipairs( rawvouchers ) do
210 | if logic.check_valid_voucher(db, rawvoucher) then
211 | return get_limit_from_rawvoucher(db, rawvoucher)
212 | end
213 | end
214 |
215 | return '0', '0', '0', '0'
216 | end
217 |
218 | return logic
219 |
--------------------------------------------------------------------------------
/pirania/files/usr/lib/lua/voucher/utils.lua:
--------------------------------------------------------------------------------
1 | #!/bin/lua
2 |
3 | utils = {}
4 |
5 | -- Used to escape "'s by toCSV
6 | local function escapeCSV (s)
7 | if string.find(s, '[,"]') then
8 | s = '"' .. string.gsub(s, '"', '""') .. '"'
9 | end
10 | return s
11 | end
12 |
13 | -- Convert from CSV string to table (converts a single line of a CSV file)
14 | local function fromCSV (s)
15 | s = s .. ',' -- ending comma
16 | local t = {} -- table to collect fields
17 | local fieldstart = 1
18 | repeat
19 | -- next field is quoted? (start with `"'?)
20 | if string.find(s, '^"', fieldstart) then
21 | local a, c
22 | local i = fieldstart
23 | repeat
24 | -- find closing quote
25 | a, i, c = string.find(s, '"("?)', i+1)
26 | until c ~= '"' -- quote not followed by quote?
27 | if not i then error('unmatched "') end
28 | local f = string.sub(s, fieldstart+1, i-1)
29 | table.insert(t, (string.gsub(f, '""', '"')))
30 | fieldstart = string.find(s, ',', i) + 1
31 | else -- unquoted; find next comma
32 | local nexti = string.find(s, ',', fieldstart)
33 | table.insert(t, string.sub(s, fieldstart, nexti-1))
34 | fieldstart = nexti + 1
35 | end
36 | until fieldstart > string.len(s)
37 | return t
38 | end
39 |
40 | -- Convert from table to CSV string
41 | local function toCSV (tt)
42 | local s = ""
43 | for _,p in ipairs(tt) do
44 | s = s .. "," .. escapeCSV(p)
45 | end
46 | return string.sub(s, 2) -- remove first comma
47 | end
48 |
49 | utils.from_csv_to_table = function(filename)
50 | local line, lines, fh, err
51 |
52 | lines = {}
53 |
54 | fh, err = io.open(filename)
55 | if err then print("OOps"); return; end
56 |
57 | while true do
58 | line = fh:read()
59 | if line == nil or line == '' then break end
60 |
61 | table.insert(lines, fromCSV(line))
62 | end
63 |
64 | fh:close()
65 |
66 | return lines
67 | end
68 |
69 | utils.from_table_to_csv = function(filename, table)
70 | local fho, err
71 | -- Open a file for write
72 | fho,err = io.open(filename, "w")
73 |
74 | for _, line in pairs( table ) do
75 | fho:write(toCSV(line))
76 | fho:write('\n')
77 | end
78 |
79 | fho:close()
80 | end
81 |
82 | utils.string_split = function(inputstr, sep)
83 | if sep == nil then
84 | sep = "%s"
85 | end
86 | local t={} ; i=1
87 | for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
88 | t[i] = str
89 | i = i + 1
90 | end
91 | return t
92 | end
93 |
94 | utils.split = function(string, sep)
95 | local ret = {}
96 | for token in string.gmatch(string, "[^"..sep.."]+") do table.insert(ret, token) end
97 | return ret
98 | end
99 |
100 | utils.redirect_page = function(url)
101 | return string.format([[
102 |
103 |
104 |
105 | Redirect
106 |
107 |
108 |
109 |
112 |
113 |
114 |
115 | ENTER
116 |
117 |
118 | ]], url, url , url)
119 | end
120 |
121 | return utils
122 |
--------------------------------------------------------------------------------
/pirania/files/usr/libexec/rpcd/pirania:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env lua
2 | --[[
3 | Copyright 2018 Marcos Gutierrez
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 | http://www.apache.org/licenses/LICENSE-3.0
8 | ]]--
9 |
10 | require "ubus"
11 | local json = require 'luci.json'
12 | local ft = require('voucher.functools')
13 | local utils = require('voucher.utils')
14 | local logic = require('voucher.logic')
15 | local dba = require('voucher.db')
16 |
17 | local uci = require('uci')
18 |
19 | local uci_cursor = uci.cursor()
20 |
21 | local function printJson (obj)
22 | print(json.encode(obj))
23 | end
24 |
25 | local conn = ubus.connect()
26 | if not conn then
27 | error("Failed to connect to ubus")
28 | end
29 |
30 | local function shell(command)
31 | -- TODO(nicoechaniz): sanitize or evaluate if this is a security risk
32 | local handle = io.popen(command)
33 | local result = handle:read("*a")
34 | handle:close()
35 | return result
36 | end
37 |
38 | local function list_vouchers(msg)
39 | local result = {}
40 | result = json.decode(shell('voucher list_vouchers'))
41 | printJson({ vouchers = result })
42 | end
43 |
44 | local function renew_voucher(msg)
45 | local name = msg.name
46 | local date = msg.date
47 | local result = {}
48 | result = json.decode(shell('voucher renew_voucher '..name..' '..date))
49 | printJson(result)
50 | end
51 |
52 | local function remove_voucher(msg)
53 | local name = msg.name
54 | local result = {}
55 | result = json.decode(shell('voucher remove_voucher '..name))
56 | printJson(result)
57 | end
58 |
59 | local function add_many_vouchers(msg)
60 | local result = {}
61 | local vouchers = ft.map(function(val) return json.encode(val) end, msg.vouchers)
62 | local voucher = io.popen("voucher add_many_vouchers", 'w')
63 | result.success = assert(voucher:write(table.concat(vouchers, '\n')))
64 | printJson(result)
65 | end
66 |
67 | local function add_voucher(msg)
68 | local result = {}
69 | local key = msg.key
70 | local secret = msg.secret
71 | local epoc = msg.epoc
72 | local upload = msg.upload
73 | local download = msg.download
74 | local amountofmacsallowed = msg.amountofmacsallowed
75 | local output = shell('voucher add_voucher '..key..' '..secret..' '..epoc..' '..download..' '..upload..' '..amountofmacsallowed)
76 | local parsed = string.gsub(output, "%s+", "")
77 | if tonumber(parsed) == 0 then
78 | result.success = false
79 | else
80 | result.key = key
81 | result.secret = secret
82 | result.epoc = epoc
83 | result.upload = upload
84 | result.download = download
85 | result.amountofmacsallowed = amountofmacsallowed
86 | end
87 | printJson(result)
88 | end
89 |
90 | local function auth_voucher(msg)
91 | local result = {}
92 | local mac = msg.mac
93 | local voucher = msg.voucher
94 | result.mac = mac
95 | result.voucher = voucher
96 | local db = dba.load(config.db)
97 | local res = logic.auth_voucher(db, mac, voucher)
98 | result.success = res.success
99 | -- BUG: Saving to DB causes ubus to timeout
100 | dba.save(config.db, db)
101 | printJson(result)
102 | end
103 |
104 | local function print_valid_macs(msg)
105 | local result = {}
106 | result.macs = {}
107 | local output = shell('voucher print_valid_macs')
108 | for line in output:gmatch("[^\n]+") do
109 | -- local words = {}
110 | -- for w in line:gmatch("%S+") do if w ~= "" then table.insert(words, w) end end
111 | -- local mac = words[2]
112 | table.insert(result.macs, line)
113 | end
114 | printJson(result);
115 | end
116 |
117 | local function show_url(msg)
118 | local result = {}
119 | local url = uci_cursor:get("pirania", "base_config", "portal_url")
120 | result.url = url
121 | printJson(result);
122 | end
123 |
124 | local function change_url(msg)
125 | local result = {}
126 | local url = msg.url
127 | uci_cursor:set("pirania", "base_config", "portal_url", url)
128 | uci_cursor:commit("pirania")
129 | result.url = url
130 | printJson(result);
131 | end
132 |
133 |
134 | local methods = {
135 | add_voucher = {
136 | key = 'value',
137 | secret = 'value',
138 | epoc = 0,
139 | upload = 0,
140 | download = 0,
141 | amountofmacsallowed = 0
142 | },
143 | add_many_vouchers = { vouchers = 'value' },
144 | auth_voucher = { mac = 'value', voucher = 'value' },
145 | print_valid_macs = { no_params = 0 },
146 | list_vouchers = { no_params = 0 },
147 | remove_voucher = { name = 'value' },
148 | renew_voucher = { name = 'value', date = 0 },
149 | show_url = { no_params = 0 },
150 | change_url = { url = 'value' },
151 | }
152 |
153 | if arg[1] == 'list' then
154 | printJson(methods)
155 | end
156 |
157 | if arg[1] == 'call' then
158 | local msg = io.read()
159 | msg = json.decode(msg)
160 | if arg[2] == 'add_voucher' then add_voucher(msg)
161 | elseif arg[2] == 'add_many_vouchers' then add_many_vouchers(msg)
162 | elseif arg[2] == 'auth_voucher' then auth_voucher(msg)
163 | elseif arg[2] == 'print_valid_macs' then print_valid_macs(msg)
164 | elseif arg[2] == 'list_vouchers' then list_vouchers(msg)
165 | elseif arg[2] == 'remove_voucher' then remove_voucher(msg)
166 | elseif arg[2] == 'renew_voucher' then renew_voucher(msg)
167 | elseif arg[2] == 'show_url' then show_url(msg)
168 | elseif arg[2] == 'change_url' then change_url(msg)
169 | else printJson({ error = "Method not found" })
170 | end
171 | end
172 |
--------------------------------------------------------------------------------
/pirania/files/usr/share/rpcd/acl.d/pirania.json:
--------------------------------------------------------------------------------
1 | {
2 | "unauthenticated": {
3 | "description": "pirania voucher public access",
4 | "write": {
5 | "ubus": {
6 | "pirania": [ "auth_voucher", "get_clients", "print_valid_macs", "show_url" ]
7 | }
8 | }
9 | },
10 | "root": {
11 | "description": "pirania administration access",
12 | "write": {
13 | "ubus": {
14 | "pirania": ["*"]
15 | }
16 | },
17 | "read": {
18 | "ubus": {
19 | "pirania": ["*"]
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pirania/files/www/pirania-redirect/redirect:
--------------------------------------------------------------------------------
1 | #!/usr/bin/lua
2 |
3 | local uci = require('uci')
4 | local json = require('luci.json')
5 | local uci_cursor = uci.cursor()
6 | local redirect_page = require('voucher.utils').redirect_page
7 |
8 | function handle_request (env)
9 | local origin_url = env.HTTP_HOST
10 | local portal_url = uci_cursor:get("pirania", "base_config", "portal_url")
11 | if origin_url ~= portal_url then
12 | local setParams = prevUrl and '?prev='..origin_url or ''
13 | local url = portal_url..setParams
14 | local send = uhttpd.send
15 | send("Status: 302 \r\n")
16 | send("Content-type: text/html \n\n")
17 | send(print(redirect_page(url)))
18 | end
19 | end
20 |
--------------------------------------------------------------------------------