Merge branch 'feature-captive-notification' into development

This commit is contained in:
Matthias Strubel 2017-07-02 10:09:55 +02:00
commit 50d54324e8
19 changed files with 444 additions and 70 deletions

View File

@ -0,0 +1,51 @@
<?php
// CLI interface to captive.func.php
//
// GPL3 (c) 2017 , Matthias Strubel <matthias.strubel@aod-rpg.de>
if ( ! ( isset ( $argv['1'] ) &&
isset ( $argv['2'] ) ) ) {
die("
Add and removes IPs to captive.sqlite database
Note: DNSMASQ is using webrequests to speed up on embedded devices!
Usage:
captive_cli.php <action> <ip> (path)
action = add / del
ip = valid ip address
path = optional, path to /opt/piratebox base folder
");
}
$action = $argv[1];
$ip = $argv[2] ;
if ( isset ( $argv[3] )) {
$path = $argv[3] ;
} else {
$path = "";
}
require_once ( $path.'./www/captive/captive.func.php');
if ( $action == "add" ) {
count_ip("$ip" , "yes" );
exit ;
} elseif ( $action == "del" ) {
del_ip("$ip" );
exit ;
} elseif ( $action == "old" ) {
// Refresh or relogin
$config = get_config();
if ( $config['old_triggers_login'] ) {
del_ip("$ip" );
count_ip("$ip" , "yes" );
}
exit;
} else {
die ("unknown action");
}

View File

@ -0,0 +1,33 @@
#!/bin/sh
# Script to notify captive notification database about lease add and del.
# If captive_notify is disabled, this script exists only.
# (to save cpu cycles).
#
# Arguments:
# add | del | old ; old is ignored
# <mac> ; ignored
# <ip> ; used
#
# GPL-3 (c)2017 , Matthias Strubel <matthias.strubel@aod-rpg.de>
#
op="${1:-op}"
ip="${3:-ip}"
. /opt/piratebox/conf/piratebox.conf
if [ "$FEATURE_CAPTIVE" = "no" ] ; then
exit 0
fi
if [ "$op" = "refresh" ] ; then
echo "Preparing Captive Portal Housekeeping file"
test -e /tmp/captive.sqlite && rm /tmp/captive.sqlite
touch /tmp/captive.sqlite ; chmod g+w /tmp/captive.sqlite
chown root:$LIGHTTPD_GROUP /tmp/captive.sqlite
exit 0
fi
echo "$op ; $ip"
wget -q -O - "http://127.0.0.1/captive/dnsmasq_cli.php?type=$op&ip=$ip" > /dev/null

View File

@ -28,7 +28,7 @@
# GEN_CHATFILE = generated html chatfile
# PIRATEBOX = PirateBox Folder
# CHATFILE = data store for Shoutbox-content
#
#
# NODE_CONFIG = Config file for Mesh-Node parameters
# -
# ipv6.conf (loaded within piratebox.conf)
@ -119,17 +119,17 @@ generate_radvd(){
mask=$2
interface=$3
echo "Generating config for radvd.."
echo "#---- generated file ---" > $RADVD_CONFIG
echo "Generating config for radvd.."
echo "#---- generated file ---" > $RADVD_CONFIG
echo "
interface $interface {
AdvSendAdvert on;
MinRtrAdvInterval 3;
MaxRtrAdvInterval 10;
prefix $prefix::/$mask {
AdvOnLink on;
AdvAutonomous on;
AdvRouterAddr on;
AdvOnLink on;
AdvAutonomous on;
AdvRouterAddr on;
};
};
" >> $RADVD_CONFIG
@ -157,21 +157,19 @@ generate_lighttpd_env() {
LIGHTTPD_ENV_BR_LINE=" \"SHOUTBOX_BROADCAST_DESTINATIONS\" => \"$GLOBAL_DEST\" , "
fi
LIGHTTPD_ENV="setenv.add-environment = (
\"PYTHONPATH\" => \"$PYTHONPATH:$PIRATEBOX/python_lib\",
\"SHOUTBOX_GEN_HTMLFILE\" => \"$SHOUTBOX_GEN_HTMLFILE\" ,
\"SHOUTBOX_CHATFILE\" => \"$SHOUTBOX_CHATFILE\" ,
\"SHOUTBOX_CLIENT_TIMESTAMP\" => \"$SHOUTBOX_CLIENT_TIMESTAMP\" ,
\"UPLOAD_PATH\" => \"$IN_UPLOAD_PATH\" ,
\"PIRATEBOX_HOSTNAME\" => \"$HOSTNAME\" ,
\"DISK_GEN_HTMLFILE\" => \"$DISK_GEN_HTMLFILE\" ,
$LIGHTTPD_ENV_BR_LINE
echo "setenv.add-environment = (
\"PYTHONPATH\" => \"$PYTHONPATH:$PIRATEBOX/python_lib\",
\"SHOUTBOX_GEN_HTMLFILE\" => \"$SHOUTBOX_GEN_HTMLFILE\" ,
\"SHOUTBOX_CHATFILE\" => \"$SHOUTBOX_CHATFILE\" ,
\"SHOUTBOX_CLIENT_TIMESTAMP\" => \"$SHOUTBOX_CLIENT_TIMESTAMP\" ,
\"UPLOAD_PATH\" => \"$IN_UPLOAD_PATH\" ,
\"PIRATEBOX_HOSTNAME\" => \"$HOSTNAME\" ,
\"DISK_GEN_HTMLFILE\" => \"$DISK_GEN_HTMLFILE\" ,
$LIGHTTPD_ENV_BR_LINE
)
var.PIRATEBOX_HOSTNAME = $HOSTNAME
"
echo $LIGHTTPD_ENV > $LIGHTTPD_ENV_CONFIG
var.PIRATEBOX_HOSTNAME = \"$HOSTNAME\"
" > $LIGHTTPD_ENV_CONFIG
}
#------------ lighttpd env config - End ---------------------
@ -179,7 +177,7 @@ generate_lighttpd_env() {
if [ -z $1 ] ; then
echo "Usage is
echo "Usage is
generate_config_files.sh /opt/piratebox/conf/piratebox.conf
"
exit 255
@ -207,7 +205,7 @@ if [ "$IPV6_ENABLE" = "yes" ] ; then
fi
generate_hosts $HOST $IP $IPV6
generate_dnsmasq $NET $IP_SHORT $START_LEASE $END_LEASE $LEASE_DURATION $DNSMASQ_INTERFACE
generate_lighttpd_env $GLOBAL_CHAT "$GLOBAL_DEST" $PIRATEBOX_PYTHONPATH $GEN_CHATFILE $PIRATEBOX_FOLDER $CHATFILE $SHOUTBOX_CLIENT_TIMESTAMP $UPLOADFOLDER $GEN_DISKFILE $HOST
generate_lighttpd_env $GLOBAL_CHAT "$GLOBAL_DEST" $PIRATEBOX_PYTHONPATH $GEN_CHATFILE $PIRATEBOX_FOLDER $CHATFILE $SHOUTBOX_CLIENT_TIMESTAMP $UPLOADFOLDER $GEN_DISKFILE $HOST
COMPLETE_HOST=$HOST
@ -217,7 +215,7 @@ if [ "$NODE_CONFIG_ACTIVE" = "yes" ] ; then
echo $NODE_GEN_OUTPUT
echo "$NODE_IPV6_IP $NODE_GEN_OUTPUT " >> $HOSTS_CONFIG
COMPLETE_HOST=$NODE_GEN_OUTPUT
else
else
echo "Error: No valid node-name-config found, skipping"
fi
fi
@ -227,6 +225,6 @@ fi
### but, the daemon works per default only on /etc/avahi
### If you want to enable avahi, then you have to link /etc/avahi to /opt/piratebox/conf/avahi
### On OpenWRT this should happen, if avahi is available before installing the piratebox
### automtically.
### automtically.
AVAHI_HOST=$( echo $COMPLETE_HOST | sed 's|\.|_|g' )
sed "s|#####MASKED_HOSTNAME#####|$AVAHI_HOST|" $AVAHI_SRC > $AVAHI_CONFIG

View File

@ -3,7 +3,7 @@
## created by Matthias Strubel (c)2011-2017 GPL-3
##
PIRATEBOX_CONFIG="/opt/piratebox/conf/piratebox.con"
PIRATEBOX_CONFIG="/opt/piratebox/conf/piratebox.conf"
create_content_folder(){

View File

@ -1,3 +1,6 @@
#Captive Portal feature hook
dhcp-script=/opt/piratebox/bin/captive_notify_lease.sh
#dont use resolv.conf
no-resolv
#dont recheck resolv.conf for changes

View File

@ -0,0 +1,29 @@
# iOS
$HTTP["useragent"] =~ "CaptiveNetworkSupport" {
server.error-handler-404 = "/captive/ia_handler.php"
}
$HTTP["url"] =~ "/hotspot-detect.html" {
server.error-handler-404 = "/captive/ia_handler.php"
}
# Android
$HTTP["url"] =~ "^/generate204" {
server.error-handler-404 = "/captive/ia_handler.php"
}
# Microsoft
$HTTP["url"] =~ "^/ncsi.txt" {
server.error-handler-404 = "/captive/ia_handler.php"
}
$HTTP["url"] =~ "^/connecttest.txt" {
server.error-handler-404 = "/captive/ia_handler.php"
}
$HTTP["url"] =~ "^/captive/ia_handler.php$" {
fastcgi.server = (
"" => ((
"bin-path" => "/usr/bin/php-cgi",
"socket" => "/tmp/php.socket",
"max-procs" => 2
))
)
}

View File

@ -1,16 +0,0 @@
#-------------------- FAST CGI stuff
# Run a specific php script when the URL /generate_204 is requested.
# Android clients request this URL to check for a full working
# internet connection, we want to fake a reply. This config section is
# a hack to make a php script without the ".php" extension work when
# mod_rewrite is not available.
$HTTP["url"] =~ "^/generate_204$" {
fastcgi.server = (
"" => ((
"bin-path" => "/usr/bin/php-cgi",
"socket" => "/tmp/php.socket",
"max-procs" => 1
))
)
}

View File

@ -1,10 +0,0 @@
# Fix for iOS7
# It ask especially different domains without a specific URL.
# It want to get a "success" message, to allow full system/internet access
$HTTP["useragent"] =~ "CaptiveNetworkSupport" {
server.document-root = "/opt/piratebox/www/library/test/"
index-file.names = ( "success.html" )
dir-listing.activate = "disable"
server.error-handler-404 = "/success.html"
}

View File

@ -78,11 +78,8 @@ include "/opt/piratebox/conf/lighttpd/all-redirect.conf"
#server.modules += ( "mod_accesslog" )
#accesslog.filename = "/opt/piratebox/tmp/access.log"
# Fix for iOS7
include "/opt/piratebox/conf/lighttpd/iOS7-fix.conf"
include "/opt/piratebox/conf/lighttpd/fastcgi-php-generate203.conf"
# Captive Portal fake answers
include "/opt/piratebox/conf/lighttpd/captive_portal.conf"
## Uncommenting the following line enables PHP for the
## complete PirateBox

View File

@ -93,6 +93,9 @@ case "$1" in
echo "Empty tmp folder"
find $PIRATEBOX/tmp/ -exec rm {} \;
# Captive Portal Housekeeping file in /tmp (ram)
"$PIRATEBOX/bin/captive_notify_lease.sh" refresh
if [ "$CUSTOM_DIRLIST_COPY" = "yes" ]; then
echo "Copy over directory design files"
$PIRATEBOX/bin/distribute_files.sh $SHARE_FOLDER/Shared
@ -240,6 +243,7 @@ case "$1" in
log_daemon_msg "Stopping dnsmasq..."
start-stop-daemon --stop --quiet --pidfile $PIDFILE_DNSMASQ
log_end_msg $?
rm "$LEASE_FILE_LOCATION"
fi
if [ -e $PIDFILE_RADVD ] ; then

View File

@ -86,6 +86,9 @@ case "$1" in
echo "Empty tmp folder"
find $PIRATEBOX/tmp/ -exec rm {} \;
# Captive Portal Housekeeping file in /tmp (ram)
"$PIRATEBOX/bin/captive_notify_lease.sh" refresh
if [ "$CUSTOM_DIRLIST_COPY" = "yes" ]; then
echo "Copy over directory design files"
$PIRATEBOX/bin/distribute_files.sh $SHARE_FOLDER/Shared
@ -220,6 +223,7 @@ case "$1" in
echo "Stopping dnsmasq..."
start-stop-daemon -K -q -p $PIDFILE_DNSMASQ
echo $?
rm "$LEASE_FILE_LOCATION"
fi

View File

@ -0,0 +1,140 @@
<?php
// Captive portal imitation database functions
// - Create functions for initializing, adding and deleting entries.
//
// GPL3 (c) 2017 , Matthias Strubel <matthias.strubel@aod-rpg.de>
// SQLITE_FILE ; SQLITE database, needs to be a PDO URI
// minimum_answers ; how much background requests needs to be issued
// for automatic "login"
// old_triggers_login ; 1 = a DHCP lease renew & rejoin should trigger
// captive portal
// 0 = Only a worn out DHCP lease triggers the
// captive portal
//
function get_config(){
$hostname="piratebox.lan";
return array (
'SQLITE_FILE' => "sqlite:/tmp/captive.sqlite" ,
'minimum_answers' => 5 ,
'old_triggers_login' => 0,
'debug' => 1,
'hostname' => "$hostname",
'captive_info_page' => "http://$hostname/content/welcome.html" ,
);
}
// Perform database connect, create if not available.
// We assume that the sqlite database is always regenerated, because it is
// located in memory.
//
function __do_db_connect() {
$config = get_config();
if ( ! $db = new PDO( $config['SQLITE_FILE'] ) ) {
print_r ( $db->errorInfo() ) ;
die ( "Error, couldn't open database " );
}
// $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sth = $db->prepare( 'CREATE TABLE IF NOT EXISTS access ( ip text PRIMARY KEY ASC, counter int )');
if ( ! $sth->execute() )
die ( "Error creating table: ". $sth->errorInfo ());
return $db;
}
// Remove entry during release of the IP via dnsmasq
function del_ip($ip){
$db=__do_db_connect();
$del_stmt = $db->prepare( "DELETE from access WHERE ip = :ip ");
if ( ! $del_stmt->execute( array ( ':ip' => $ip ) ) ) {
die ( "Error deleting IP $ip");
}
return 0;
}
// Check if IP needs an captive portal answer,
// or we are still sending redirect answers.
//
// 1 = Send fake reply
// 0 = Send redirect
function check_ip_send_fake($ip){
$config = get_config();
$db=__do_db_connect();
$sel_sth= $db->prepare( "SELECT ip, counter FROM access WHERE ip = :ip ");
if ( ! $sel_sth->execute( array ( ':ip' => $ip ) ) ) {
die( "Error getting IP entry: " . $sel_sth->errorInfo());
}
$cnt = 0;
if ( $row = $sel_sth->fetch(PDO::FETCH_ASSOC) ) {
$cnt = $row['counter'];
} else {
$cnt = 0;
// This should not happen, because we get an entry through dnsmasq
}
if ( $cnt > $config['minimum_answers'] ) {
return 1;
} else {
return 0;
}
}
// This function is controlling the IP entry in the database.
//
// It is called by dnsmasq to do the initial insert:
// count_ip($ip, "yes");
//
// It is called by iac_handler.php via
// count_ip($ip);
//
// And the enter.php for unlocking via captive browser
// count_ip($ip,,"99");
//
function count_ip($ip, $do_only_insert="no" , $amount=1 ){
$db=__do_db_connect();
$insert="INSERT INTO access ( ip , counter ) VALUES ( :ip , :cnt ) ";
$stmt="";
$cnt = 0;
if ( $do_only_insert == "no" ) {
$sel_sth= $db->prepare( "SELECT ip, counter FROM access WHERE ip = :ip ");
if ( ! $sel_sth->execute( array ( ':ip' => $ip ) ) ) {
die( "Error getting IP entry: " . $sel_sth->errorInfo());
}
if ( $row = $sel_sth->fetch(PDO::FETCH_ASSOC) ) {
$cnt = $row['counter'] + $amount;
$stmt= "UPDATE access SET counter = :cnt WHERE ip = :ip ";
} else {
$cnt = 1;
$stmt=$insert;
// This should not happen, because we get an entry through dnsmasq
}
} elseif ( $do_only_insert == "yes" ) {
// This is used to avoid an additional select & update,
// because dnsmasq is calling this before the client gets an IP.
$stmt = $insert;
} else {
die ("unexpected function call with do_only_insert= $do_only_insert");
}
$up_stmt = $db->prepare( "$stmt" );
if ( ! $up_stmt->execute ( array ( ':ip' => $ip , ':cnt' => $cnt ))) {
die ("Error updating table with counter $cnt". $up_stmt->errorInfo ());
}
return 0;
}
function erdebug($string="") {
$config=get_config();
if ( $config['debug'] ) {
error_log($string);
}
}
?>

View File

@ -0,0 +1,46 @@
<?php
//Templates for generating correct answers on internet detection
//
// GPL3 (c)2017 Matthias Strubel matthias.strubel@aod-rpg.de
function template_iOS_background(){
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>Success</TITLE>
</HEAD>
<BODY>
Success
</BODY>
</HTML>
<?php
}
function template_iOS_captiveBrowser(){
header('Location: http://'.$config['hostname'].'/', true, 302);
}
function template_Android(){
http_response_code(204);
}
function template_MSphone(){
print ("Microsoft NCSI");
}
function template_MSWin10(){
print ("Microsoft Connect Test");
}
function template_none(){
print ("<html><body><pre>");
print ("Not defined, but OK.");
print ("Report the following to piratebox.cc: ");
print (" - REMOTE_ADDR - ". $_SERVER['REMOTE_ADDR'] );
print (" - REQUEST_URI - ". $_SERVER['REQUEST_URI'] );
print (" - HTTP_USER_AGENT - ". $_SERVER['HTTP_USER_AGENT'] );
print (" - SERVER_NAME - ". $_SERVER['SERVER_NAME'] );
print ("</pre></body></html>");
}

View File

@ -0,0 +1,43 @@
<?php
// general handler to give answers to "internet available checks"
//
// GPL3 (C) 2017 Matthias Strubel <matthias.strubel@aod-rpg.de>
//
// $_SERVER['REMOTE_ADDR'] - Clients IP
if ( $_SERVER['REMOTE_ADDR'] != '127.0.0.1' ) {
echo "403";
exit;
}
require_once ("captive.func.php");
$config = get_config();
$action = $_GET['type'];
$ip = $_GET['ip'];
if ( $action == "add" ) {
count_ip("$ip" , "yes" );
exit ;
} elseif ( $action == "del" ) {
del_ip("$ip" );
exit ;
} elseif ( $action == "old" ) {
// Refresh or relogin
$config = get_config();
if ( $config['old_triggers_login'] ) {
del_ip("$ip" );
count_ip("$ip" , "yes" );
}
exit;
} else {
die ("unknown action");
}
?>

View File

@ -0,0 +1,60 @@
<?php
// general handler to give answers to "internet available checks"
//
// GPL3 (C) 2017 Matthias Strubel <matthias.strubel@aod-rpg.de>
//
// $_SERVER['REMOTE_ADDR'] - Clients IP
// $_SERVER['REQUEST_URI'] - URL , needed for Client OS
// $_SERVER['HTTP_USER_AGENT'] - needed for Client OS verification
// ( $_SERVER['SERVER_NAME'] - possibly )
require_once ("captive.func.php");
$config = get_config();
if ( isset ($_GET['enter'] ) ) {
header('Location: http://piratebox.lan/', true, 302);
count_ip($_SERVER['REMOTE_ADDR'],"no",99);
exit;
}
$send = check_ip_send_fake($_SERVER['REMOTE_ADDR']);
erdebug('DEBUG LOG FOR IA_HANDLER');
erdebug(" - REMOTE_ADDR - ". $_SERVER['REMOTE_ADDR'] );
erdebug(" - REQUEST_URI - ". $_SERVER['REQUEST_URI'] );
erdebug(" - HTTP_USER_AGENT - ". $_SERVER['HTTP_USER_AGENT'] );
erdebug(" - SERVER_NAME - ". $_SERVER['SERVER_NAME'] );
$client_type="none";
if ( preg_match ( '/CaptiveNetworkSupport/' ,$_SERVER['HTTP_USER_AGENT'] )) {
$client_type="iOS_background";
} elseif ( "/hotspot-detect.html" == $_SERVER['REQUEST_URI'] ){
$client_type="iOS_captiveBrowser";
} elseif ( "/generate204" == $_SERVER['REQUEST_URI'] ){
$client_type="Android" ;
} elseif ( "/ncsi.txt" == $_SERVER['REQUEST_URI'] ){
$client_type="MSphone";
} elseif ( "/connecttest.txt" == $_SERVER['REQUEST_URI'] ){
$client_type="MSWin10";
}
erdebug( "Detected client type: $client_type");
if ( $send == 0 ) {
// Send redirect
erdebug( " -> Send redirect");
header("Location: ". $config['captive_info_page'] , true, 302);
count_ip($_SERVER['REMOTE_ADDR']);
exit;
}
// Access confirmed, send fake reply
require_once ("captive.templates.php");
erdebug( " -> Send answer");
call_user_func( "template_$client_type");

View File

@ -1,5 +0,0 @@
<?php
// Return an empty page to fake a working internet connection for
// android
http_response_code(204);
?>

View File

@ -1,9 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>Success</TITLE>
</HEAD>
<BODY>
Success
</BODY>
</HTML>

View File

@ -1 +0,0 @@
Microsoft NCSI

View File

@ -0,0 +1,7 @@
<html>
<head><title>Welcome</title></head>
<body>
spacer page
<h1><a href="/captive/ia_handler.php?enter=1">ENTER</a></h1>
</body>
</html>