3. Manual installation - Gguardiola/Sidius-CaptivePortal GitHub Wiki



NOTE: Check the requirements before continue!

REMEMBER TO FOLLOW THIS WIKI AS ROOT USER!

Dependences

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

LAMP

Install apache2

apt-get install apache2

Install MySQL

apt-get install mysql-server mysql-client

Configure MySQL Server

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.

Install PHP

apt-get install php libapache2-mod-php php-mysql php-cli

Changing the index extension priority

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

DNS

Install Bind9

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

Forwarders

At this point, you have two options to forward your DNS outside the network:

  • Forward to a Normal DNS

  • Forward to a Filtering DNS

Forward to a Normal 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; };
};

Forward to a Filtering DNS

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";
	};
};

OPTIONAL: Create your own response-policy

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

DHCP

Install isc-dhcp-server

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

Cloning repository

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

Putting SSL certificate into Apache2

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

Security, logs & permissions

Logs

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

Permissions

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

Security

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

Making iptables persistent at startup

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

Handling of the payment gateway

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

First run

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)

Fill the form pages to finalize the captive portal config



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!


Deleting first_run.php

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.

Deleting UNPAID status users

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)

Admin management

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!

⚠️ **GitHub.com Fallback** ⚠️