3. Manual installation - Gguardiola/Sidius-CaptivePortal GitHub Wiki
- Dependences
- LAMP
- DNS
- DHCP
- Cloning repository
- Putting SSL certificate into Apache2
- Security, logs & permissions
- Handling of the payment gateway
- First run
NOTE: Check the requirements before continue!
REMEMBER TO FOLLOW THIS WIKI AS ROOT USER!
To install the captive portal, first of all you need to install these packages:
apt-get install vnstat
apt-get install ifstat
apt-get install python3 python3-pip
apt-get install apache2
apt-get install mysql-server mysql-client
sudo mysql_secure_installation
Now MySQL will ask you to configure things like the root password, delete anonymous users...
VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?
Press y|Y for Yes, any other key for No: Y
We recommend password strong level 1
There are three levels of password validation policy:
LOW Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Using existing password for root.
Estimated strength of the password: 100
Change the password for root ? ((Press y|Y for Yes, any other key for No) : y
For the following questions, we recommend to choose Y.
apt-get install php libapache2-mod-php php-mysql php-cli
nano /etc/apache2/mods-enabled/dir.conf
Move the index.php to the first place.
<IfModule mod_dir.c>
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm
</IfModule>
Restart the Apache2 service
systemctl restart apache2
More info here: Digital Ocean LAMP tutorial
apt-get install bind9
Now we need to create the primary zone and the reverse zone.
cd /etc/bind
Create the primary zone file
NOTE: you can copy the example file and fill it with your new config
cp db.empty direct.db
nano direct.db
Now fill the file with your custom config, where jgtektest.webredirect.org is your domain name and 192.168.1.1 is your internal interface IP address.
; BIND reverse data file for empty rfc1918 zone
;
; DO NOT EDIT THIS FILE - it is used for multiple zones.
; Instead, copy it, edit named.conf, and use that copy.
;
$TTL 86400
@ IN SOA jgtektest.webredirect.org. root.jgtektest.webredirect.org. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
86400 ) ; Negative Cache TTL
;
@ IN NS dns.jgtektest.webredirect.org.
dns IN A 192.168.1.1
@ IN A 192.168.1.1
www IN CNAME jgtektest.webredirect.org.
Copy the same db.empty as reverse.db and edit it like this
; BIND reverse data file for empty rfc1918 zone
;
; DO NOT EDIT THIS FILE - it is used for multiple zones.
; Instead, copy it, edit named.conf, and use that copy.
;
$TTL 86400
@ IN SOA jgtektest.webredirect.org. root.jgtektest.webredirect.org. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
86400 ) ; Negative Cache TTL
;
@ IN NS dns.jgtektest.webredirect.org.
1 IN PTR dns.jgtektest.webredirect.org.
1 IN PTR www.jgtektest.webredirect.org.
1 IN PTR jgtektest.webredirect.org.
Declare the new zones into named.conf.local
nano named.conf.local
//
// Do any local configuration here
//
// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";
zone "jgtektest.webredirect.org." {
type master;
file "/etc/bind/direct.db";
};
zone "1.168.192.in-addr.arpa" {
type master;
file "/etc/bind/reverse.db";
};
NOTE: On 1.168.192.in-addr.arpa you need to write your internal interface IP address backwards excepting the host side.
Example:
192.168.1.1/24 - 1.168.192.in-addr.arpa
10.110.0.1/16 - 110.10.in-addr.arpa
At this point, you have two options to forward your DNS outside the network:
-
Forward to a Normal DNS
-
Forward to a Filtering DNS
With this forward, your captive portal will accept any kind of search through internet.
nano named.conf.options
Forward your DNS server to Google's DNS
options {
directory "/var/cache/bind";
// If there is a firewall between you and nameservers you want
// to talk to, you may need to fix the firewall to allow multiple
// ports to talk. See http://www.kb.cert.org/vuls/id/800113
// If your ISP provided one or more IP addresses for stable
// nameservers, you probably want to use them as forwarders.
// Uncomment the following block, and insert the addresses replacing
// the all-0's placeholder.
forwarders {
8.8.8.8;
8.8.4.4;
};
//========================================================================
// If BIND logs error messages about the root key being expired,
// you will need to update your keys. See https://www.isc.org/bind-keys
//========================================================================
dnssec-validation auto;
auth-nxdomain no; # conform to RFC1035
listen-on-v6 { any; };
};
This is no big deal, it's the same as a Normal DNS but we use as forwarders the DNS who already filters requests [OpenDNS Family Guard, CloudFlare Gateway, Sophos] who blocks (adult sites, casino, phising, etc..)
options {
directory "/var/cache/bind";
// If there is a firewall between you and nameservers you want
// to talk to, you may need to fix the firewall to allow multiple
// ports to talk. See http://www.kb.cert.org/vuls/id/800113
// If your ISP provided one or more IP addresses for stable
// nameservers, you probably want to use them as forwarders.
// Uncomment the following block, and insert the addresses replacing
// the all-0's placeholder.
forwarders {
208.67.222.123;
208.67.220.123;
};
//========================================================================
// If BIND logs error messages about the root key being expired,
// you will need to update your keys. See https://www.isc.org/bind-keys
//========================================================================
dnssec-validation no;
auth-nxdomain no; # conform to RFC1035
listen-on-v6 { none; };
recursion yes;
response-policy {
zone "familyfriendly";
};
};
Apart from forwarding the DNS requests to the outside, we can create our response-policy, so requests will be able to get filtered, so if we have a adult site and we don't want the people to access it, we will be able block the domain, (users still will get able to access via Direct IP but we filter the 99% of the people)
What we are going to do now is to create a response-policy zone on the bind9 so we can filter/block requests to domains that we don't want to be accessible to the captive portal users.
As the primary and reverse zone, you need to create a new one that we will use to filter the requsts.
cp db.empty familyfriendly.db
; BIND reverse data file for empty rfc1918 zone
;
; DO NOT EDIT THIS FILE - it is used for multiple zones.
; Instead, copy it, edit named.conf, and use that copy.
;
; We don't need to change the localhost because this is an internal zone only accessible for the server.
$TTL 3600
@ IN SOA localhost. root.localhost. (
202002802 ; Serial
1800 ; Refresh
3600 ; Retry
2419200 ; Expire
3600 ) ; Negative Cache TTL
;
@ IN NS localhost.
www.youtube.com IN CNAME restrictmoderate.youtube.com.
m.youtube.com IN CNAME restrictmoderate.youtube.com.
youtubei.googleapis.com IN CNAME restrictmoderate.youtube.com.
youtube.googleapis.com IN CNAME restrictmoderate.youtube.com.
www.youtube-nocookie.com IN CNAME restrictmoderate.youtube.com.
; EXAMPLES:
;xvideos.com IN CNAME google.com.
;xvideos.com IN CNAME *. -> DNS answers the domain exists but there wasn't any answer
;xvideos.com IN CNAME . -> DNS answers the domain doesn't exist.
;xvideos.com IN CNAME rpz-drop. -> DNS doesn't even answer to this specific request.
;
;# There are another rpz methods for DNS responses but you can search it on the web, those are more than enough.
Declare the new file into named.conf.local
Just add it at the end of the file!
zone "familyfriendly" IN {
type master;
file "/etc/bind/familyfriendly.db";
};
Restart the DNS service
systemctl restart bind9
apt-get install isc-dhcp-server
Edit the DHCP config file
nano /etc/dhcp/dhcpd.conf
Now we need to declare the network configuration and IP range. Remember to put your internal interface IP address as option routers and option domain-name-servers
# DHCP Declaration
subnet 192.168.1.1 netmask 255.255.255.0 {
range 192.168.1.20 192.168.1.254;
option domain-name-servers 192.168.1.1;
option routers 192.168.1.1;
default-lease-time 600;
max-lease-time 3600;
}
Indicate the listening interface
nano /etc/default/isc-dhcp-server
In INTERFACESv4="" we need to put the internal interface name. Example: enp0s8
INTERFACESv4="enp0s8"
INTERFACESv6=""
Restart the DHCP service
systemctl restart isc-dhcp-server
Check if you have Git installed
apt-get install git
Clone the repository
git clone https://github.com/Gguardiola/Sidius-CaptivePortal.git
Move the folder to the Apache2 web folder
mv Sidius-CaptivePortal /var/www/html/captiveportal
Assuming that you have the .pem SSL keys, we need to do the following stuff:
a2enmod ssl
a2enmod rewrite
cd /etc/apache2/sites-enabled
a2dissite 000-default.conf
cd /etc/apache2/sites-available
nano default-ssl.conf
Declare the SSL keys location and redirect the HTTP trafic to the HTTPS page
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName jgtektest.webredirect.org
DocumentRoot /var/www/html/captiveportal
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/jgtektest.webredirect.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/jgtektest.webredirect.org/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
<VirtualHost :80>
DocumentRoot /var/www/html/captiveportal
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteEngine on
RewriteRule ^(.)$ https://jgtektest.webredirect.org/ [L,R=301]
RewriteCond %{HTTPS_HOST} !jgtektest.webredirect.org [NC]
</VirtualHost>
</IfModule>
On SSLCertificateFile and SSLCertificateKeyFile you need to put the SSL keys location.
Now promote this file to be the web server main page
a2ensite default-ssl.conf
Restart the Apache2 service
systemctl restart apache2
SIDIUS uses custom logs to storage stuff like the login attempts or the iptables rules.
touch /var/log/captiveportal.log
touch /var/log/iptablesRules.log
Give permissions to the log files
chown www-data:www-data /var/log/captiveportal.log
chown www-data:www-data /var/log/iptablesRules.log
Give permissions to the captive portal folder
cd /var/www/html/
chown -R www-data:www-data captiveportal
Give MySQL root user access from outside
mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by 'password';
\q
Give permissions to www-data to execute iptables commands
sudo visudo
WARNING: This file is VERY DELICATE if you type wrong any letter, you will never can log in as root!
Add this at the end of the file
www-data ALL=NOPASSWD: /sbin/iptables
Avoid users entering the "Index of" folder browser
nano /etc/apache2/apache2.conf
Find "Indexes" and delete it
<Directory /var/www>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
Configure iptables script
cd /var/www/html/captiveportal/cpanel
chmod +x firewall.sh
nano firewall.sh
At the top of this file, we have the variables that defines the firewall behavior. Put the values that correspond to the configuration of your machine.
#external interface
EXTERNAL=enp0s3
#internal interface
INTERNAL=enp0s8
#external subnet IP and subnet mask
EXTERNALNET=10.110.0.0/16
#internal interface IP address
INTERNALIP=10.222.0.1
#external gateway (router IP) and subnet mask
GATEWAYIP=10.110.0.1/16
#OPTIONAL: IP of the machine that can log in remotely (if you dont want anyone, you can write 127.0.0.1)
SSHIP=10.110.0.10/32
#PAYMENT GATEWAY DOMAIN: this is to allow access to the payment gateway without having access to the rest of the internet
PAYMENTGATEWAY=sis-t.redsys.es
Basic DNS forwarders
DNSFORWARDER1=8.8.8.8
DNSFORWARDER2=8.8.4.4
Family Shield DNS forwarders
DNSFORWARDER1=208.67.222.123
DNSFORWARDER2=208.67.220.123
Now apply the iptables script
./firewall.sh
Iptables have the problem that the rules aren't persistent when you reboot your computer. We also can't use iptables-persistent because it starts before Bind9 (DNS server). This causes that iptables doesn't resolve the domains of the rules and finally blows up.
Solution:
Make a systemd service that starts after network service
Create the service file
nano /etc/systemd/system/sidiusfirewall.service
[Unit]
Description=SIDIUS captive portal iptables rules trigger
After=network.target
[Service]
Type=oneshot
ExecStart=/var/www/html/captiveportal/cpanel/firewall.sh
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable sidiusfirewall.service
Check if it works
Reboot your computer. After startup, execute iptables -L and if something like the following comes out it means it has worked.
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT all -- anywhere anywhere
REJECT tcp -- anywhere anywhere #conn src/32 > 10 reject-with tcp-reset
REJECT tcp -- anywhere anywhere #conn src/32 > 10 reject-with tcp-reset
ACCEPT udp -- anywhere anywhere udp dpt:ntp
ACCEPT icmp -- anywhere anywhere
ACCEPT tcp -- 10.110.0.10 anywhere tcp dpt:ssh
ACCEPT udp -- dns.google anywhere udp spt:domain
ACCEPT udp -- dns.google anywhere udp spt:domain
ACCEPT tcp -- dns.google anywhere tcp spt:domain
ACCEPT tcp -- dns.google anywhere tcp spt:domain
ACCEPT udp -- anywhere anywhere udp dpt:domain
ACCEPT tcp -- anywhere anywhere tcp dpt:domain
ACCEPT udp -- anywhere anywhere udp dpt:bootps
ACCEPT udp -- anywhere anywhere udp dpt:bootpc
ACCEPT tcp -- anywhere anywhere tcp dpt:https
ACCEPT tcp -- anywhere anywhere tcp dpt:http
ACCEPT tcp -- anywhere anywhere tcp spt:https
ACCEPT tcp -- anywhere anywhere tcp spt:http
DROP all -- anywhere anywhere state INVALID
Chain FORWARD (policy DROP)
target prot opt source destination
DROP all -- anywhere anywhere state INVALID
ACCEPT tcp -- anywhere 195.76.9.247
ACCEPT tcp -- anywhere mad07s09-in-f3.1e100.net
ACCEPT tcp -- anywhere mad07s10-in-f19.1e100.net
ACCEPT tcp -- anywhere vip0x00f.map2.ssl.hwcdn.net
ACCEPT tcp -- anywhere vip0x018.map2.ssl.hwcdn.net
ACCEPT tcp -- anywhere 104.16.86.20
ACCEPT tcp -- anywhere 104.16.89.20
ACCEPT tcp -- anywhere 104.16.87.20
ACCEPT tcp -- anywhere 104.16.85.20
ACCEPT tcp -- anywhere 104.16.88.20
ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy DROP)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT udp -- anywhere anywhere udp spt:ntp
ACCEPT icmp -- anywhere anywhere
ACCEPT tcp -- anywhere 10.110.0.10 tcp spt:ssh
ACCEPT udp -- anywhere dns.google udp dpt:domain
ACCEPT udp -- anywhere dns.google udp dpt:domain
ACCEPT tcp -- anywhere dns.google tcp dpt:domain
ACCEPT tcp -- anywhere dns.google tcp dpt:domain
ACCEPT udp -- anywhere anywhere udp spt:domain
ACCEPT tcp -- anywhere anywhere tcp spt:domain
ACCEPT udp -- anywhere anywhere udp spt:bootps
ACCEPT udp -- anywhere anywhere udp spt:bootpc
ACCEPT tcp -- anywhere anywhere tcp spt:https
ACCEPT tcp -- anywhere anywhere tcp spt:http
ACCEPT tcp -- anywhere anywhere tcp dpt:https
ACCEPT tcp -- anywhere anywhere tcp dpt:http
DROP all -- anywhere anywhere state INVALID
SIDIUS uses RedSys by default. We don't give support to any other payment gateway. You can explore how to implement other payment gateway API and merge it to SIDIUS. We can understand that maybe RedSys doesn't works in some countries but we can't make it better.
For handling the payments of the clients, you need to edit tpv.php into /signup folder
Keep in mind that you need to contact to your bank office to get the SHA key, code and terminal!
When your payment gateway is ready to go, remember to uncomment the $url_tpv to production!
//if $plan exists it means that the user selected a plan and confirmed the privacy policy and TOS.
if(isset($plan)){
//loading the API
include "redsys-api/apiRedsys.php";
$myObj = new RedsysAPI;
//payment gateway for testing
//COMMENT THIS VARIABLE BELOW
$url_tpv = 'https://sis-t.redsys.es:25443/sis/realizarPago';
//payment gateway for production
//UNCOMMENT THIS VARIABLE BELOW
//$url_tpv = 'https://sis.redsys.es/sis/realizarPago';
$version = "HMAC_SHA256_V1";
//this key is provided by the bank (in this case is a testing key)
//CHANGE THIS FOR THE KEY THAT BANK PROVIDED YOU
$key = 'sq7HjrUOBfKmC576ILgskD5srU870gJ7';
//$name is the name of the commerce. In this case gets the captive portal name from the config.php
$name = $config['captivename'];
//testing merchant code and terminal
//CHANGE THIS FOR THE CODE THAT BANK PROVIDED YOU
$code = '999008881';
//CHANGE THIS FOR THE CODE THAT BANK PROVIDED YOU
$terminal='001';
//the order code is a combination of the current time without format and the letter of the selected plan
$order= time();
////////// PLAN CONFIGURATION //////////
$db = mysqli_connect($config['db_server'], $config['db_username'], $config['db_password'], $config['db_name']);
# In case of error connecting send client back to login.
if (!$db) {
#### WIP: error_log("ERROR: Database is not available!", 3, $config['error_log']); ####
print("ERROR: Database is not available.");
}
# Database is up, querying...
You can know how to change the plan pricing and bandwidth here
Congratulations! you are coming to the end!
Through a computer with graphical environment (it can be the server) go to your captive portal website and add /first_run.php at the end (example.org/first_run.php)
NOTE: remember to put the family friendly DNS forwaders on the last form inputs! (208.67.222.123 and 208.67.220.123)
Finally you have a full operating captive portal!
For security reasons, you need to delete first_run.php
cd /var/www/html/captiveportal
rm first_run.php
If in the future you want to reconfigure the captive portal, come back to this repository and download it.
If the user tries to register with STANDARD or PRO plan and for some reason doesn't completes the payment, when he/she tries to log in, the captive portal will warn that needs to complete it through "Account settings" button.
But, we need to make the MySQL event to make this possible!
mysql -u root -p
CREATE EVENT DeleteUNPAID on schedule every 10 minute do DELETE FROM login WHERE creation_date > now() - INTERVAL 7 day AND payment_status = "UNPAID";
NOTE: Where login you need to set your auth table name (if you have the same leave it like this)
You can log in as admin and enter the CPANEL clicking the button after logging in.
Here you can manage the system status, change the concession time of the plans, remove concessions, check the logs and the database users!