mirror of
https://github.com/hak5/nano-tetra-modules.git
synced 2025-10-29 16:58:09 +00:00
File diff suppressed because it is too large
Load Diff
188
EvilPortal/executable/executable
Normal file → Executable file
188
EvilPortal/executable/executable
Normal file → Executable file
@@ -1,133 +1,99 @@
|
||||
#!/usr/bin/python
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Evil Portal
|
||||
# Newbi3
|
||||
# This is the python control script to handle all iptables things for creating and managing a Captive Portal
|
||||
#
|
||||
#Modified by oXis for the Wifi Pineapple (OpenWRT)
|
||||
|
||||
import sys
|
||||
import os
|
||||
# Written by Sitwon and The Doctor.
|
||||
# Copyright (C) 2013 Project Byzantium
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
|
||||
|
||||
arp () { cat /proc/net/arp; } # arp function
|
||||
|
||||
CLIENTS_FILE = "/tmp/EVILPORTAL_CLIENTS.txt"
|
||||
WHITE_LIST = ["172.16.42.42"]
|
||||
IPTABLES=/usr/sbin/iptables
|
||||
ARP=arp
|
||||
IP=172.16.42.1
|
||||
|
||||
case "$1" in
|
||||
'init')
|
||||
|
||||
def revoke_client(ip_address=None):
|
||||
global CLIENTS_FILE
|
||||
# Convert the IP address of the client interface into a netblock.
|
||||
CLIENTNET=`echo $IP | sed 's/1$/0\/24/'`
|
||||
|
||||
if not ip_address:
|
||||
print "An ipaddress is expected."
|
||||
return
|
||||
# Exempt traffic which does not originate from the client network.
|
||||
$IPTABLES -t mangle -I PREROUTING -p all ! -s $CLIENTNET -j RETURN
|
||||
|
||||
f = open(CLIENTS_FILE, 'r')
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
# Traffic not coming from an accepted user gets marked 99.
|
||||
$IPTABLES -t mangle -A fwmark -j MARK --set-mark 99
|
||||
|
||||
f = open(CLIENTS_FILE, 'w')
|
||||
for line in lines:
|
||||
if line == ip_address + "\n":
|
||||
os.system("iptables -t nat -D PREROUTING -s " + line.rstrip('\n') + " -j ACCEPT")
|
||||
else:
|
||||
f.write(line)
|
||||
f.close()
|
||||
# Traffic which has been marked 99 and is headed for 80/TCP or 443/TCP
|
||||
# should be redirected to the captive portal web server.
|
||||
$IPTABLES -t nat -A prerouting_rule -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination $IP:80
|
||||
# Need to activate HTTPS on the nginx server of the PineAP, so for now HTTPS traffic is dropped.
|
||||
#$IPTABLES -t nat -A prerouting_rule -m mark --mark 99 -p tcp --dport 443 -j DNAT --to-destination $IP:443
|
||||
|
||||
# for use with dns spoff
|
||||
$IPTABLES -t filter -A forwarding_rule -p udp --dport 53 -j ACCEPT
|
||||
$IPTABLES -t nat -A prerouting_rule -m mark --mark 99 -p udp --dport 53 -j DNAT --to-destination $IP:53
|
||||
|
||||
def authorize_client(ip_address=None):
|
||||
global CLIENTS_FILE
|
||||
$IPTABLES -t filter -A input_rule -p tcp --dport 80 -j ACCEPT #Webserver
|
||||
#$IPTABLES -t filter -A input_rule -p tcp --dport 443 -j ACCEPT #Webserver
|
||||
$IPTABLES -t filter -A input_rule -p tcp --dport 1471 -j ACCEPT #PineAP admin page
|
||||
$IPTABLES -t filter -A input_rule -p tcp --dport 22 -j ACCEPT #SSH
|
||||
|
||||
if not ip_address:
|
||||
print "An ipaddress is expected."
|
||||
return
|
||||
# All other traffic which is marked 99 is just dropped
|
||||
$IPTABLES -t filter -A forwarding_rule -m mark --mark 99 -j DROP
|
||||
# Even on INPUT rule
|
||||
$IPTABLES -t filter -A input_rule -m mark --mark 99 -j DROP
|
||||
|
||||
f = open(CLIENTS_FILE, 'a+')
|
||||
lines = f.readlines()
|
||||
if ip_address + "\n" not in lines:
|
||||
os.system("iptables -t nat -I PREROUTING -s " + ip_address + " -j ACCEPT")
|
||||
f.write(ip_address + "\n")
|
||||
f.close()
|
||||
exit 0
|
||||
;;
|
||||
'add')
|
||||
# $2: IP address of client.
|
||||
CLIENT=$2
|
||||
|
||||
# Isolate the MAC address of the client in question.
|
||||
CLIENTMAC=`$ARP -n | grep ':' | grep $CLIENT | awk '{print $4}'`
|
||||
|
||||
def stop_evilportal():
|
||||
"""
|
||||
stop_evilportal
|
||||
Stop EvilPortals
|
||||
"""
|
||||
global CLIENTS_FILE, WHITE_LIST
|
||||
# Add the MAC address of the client to the whitelist, so it'll be able
|
||||
# to access the mesh even if its IP address changes.
|
||||
$IPTABLES -t mangle -I fwmark -m mac --mac-source $CLIENTMAC -j RETURN
|
||||
$IPTABLES -A INPUT -m mac --mac-source 74:da:38:5a:03:66 -p udp --dport 53 -j ACCEPT
|
||||
|
||||
if os.path.isfile(CLIENTS_FILE):
|
||||
# Remove rule for each accepted client
|
||||
[os.system("iptables -t nat -D PREROUTING -s " + line.rstrip('\n') + " -j ACCEPT") for line in open(CLIENTS_FILE, 'r')]
|
||||
exit 0
|
||||
;;
|
||||
'remove')
|
||||
# $2: IP address of client.
|
||||
CLIENT=$2
|
||||
|
||||
# Delete the clients file
|
||||
os.remove(CLIENTS_FILE)
|
||||
# Isolate the MAC address of the client in question.
|
||||
CLIENTMAC=`$ARP -n | grep ':' | grep $CLIENT | awk '{print $4}'`
|
||||
|
||||
# Stop HTTP Redirection
|
||||
os.system("iptables -t nat -D PREROUTING -s 172.16.42.0/24 -p tcp --dport 80 -j DNAT --to-destination 172.16.42.1:80")
|
||||
# Delete the MAC address of the client from the whitelist.
|
||||
$IPTABLES -t mangle -D fwmark -m mac --mac-source $CLIENTMAC -j RETURN
|
||||
|
||||
# Remove DNS Policy
|
||||
os.system("iptables -D INPUT -p tcp --dport 53 -j ACCEPT")
|
||||
exit 0
|
||||
;;
|
||||
'purge')
|
||||
CLIENTNET=`echo $IP | sed 's/1$/0\/24/'`
|
||||
# Purge the user defined chains
|
||||
$IPTABLES -t mangle -F fwmark
|
||||
$IPTABLES -t nat -F prerouting_rule
|
||||
$IPTABLES -t filter -F input_rule
|
||||
$IPTABLES -t filter -F forwarding_rule
|
||||
$IPTABLES -t mangle -D PREROUTING -p all ! -s $CLIENTNET -j RETURN
|
||||
|
||||
$IPTABLES -t nat -D prerouting_rule -m mark --mark 99 -p udp --dport 53 -j DNAT --to-destination $IP:53
|
||||
|
||||
def start_evilportal():
|
||||
"""
|
||||
start_evilportal
|
||||
Start EvilPortals IP table based captive portal
|
||||
"""
|
||||
global CLIENTS_FILE, WHITE_LIST
|
||||
exit 0
|
||||
;;
|
||||
'list')
|
||||
# Display the currently running IP tables ruleset.
|
||||
$IPTABLES --list -t nat -n
|
||||
$IPTABLES --list -t mangle -n
|
||||
$IPTABLES --list -t filter -n
|
||||
|
||||
if os.path.isfile(CLIENTS_FILE):
|
||||
os.remove(CLIENTS_FILE)
|
||||
|
||||
# Make sure forwarding is enabled which it should be but just to be sure do it here
|
||||
os.system("echo 1 > /proc/sys/net/ipv4/ip_forward")
|
||||
|
||||
# Setup the iptables
|
||||
# Set white listed clients
|
||||
f = open(CLIENTS_FILE, "w")
|
||||
for client in WHITE_LIST:
|
||||
os.system("iptables -A INPUT -s " + client + " -j ACCEPT")
|
||||
f.write(client + "\n")
|
||||
f.close()
|
||||
|
||||
# Redirect all web traffic to port 80 on the pineapple
|
||||
os.system("iptables -t nat -A PREROUTING -s 172.16.42.0/24 -p tcp --dport 80 -j DNAT --to-destination 172.16.42.1:80")
|
||||
|
||||
# Accept dns
|
||||
os.system("iptables -A INPUT -p tcp --dport 53 -j ACCEPT")
|
||||
|
||||
|
||||
def handler(args=None):
|
||||
cmd = None
|
||||
param = None
|
||||
if args is not None:
|
||||
try:
|
||||
cmd = args[1].lower()
|
||||
except IndexError as e:
|
||||
cmd = "help"
|
||||
try:
|
||||
param = args[2].lower()
|
||||
except IndexError as e:
|
||||
pass
|
||||
else:
|
||||
cmd = "start"
|
||||
|
||||
commands = {"start": "Start EvilPortal", "stop": "Stop EvilPortal", "help": "What you are reading"}
|
||||
|
||||
if cmd == "start":
|
||||
start_evilportal()
|
||||
elif cmd == "stop":
|
||||
stop_evilportal()
|
||||
elif cmd == "authorize":
|
||||
authorize_client(param)
|
||||
elif cmd == "help":
|
||||
print "-"*20
|
||||
print "Evil Portal"
|
||||
print "-"*20
|
||||
for command, description in commands.iteritems():
|
||||
print command + ":\t" + description
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
handler(sys.argv)
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "USAGE: $0 {initialize|add <IP>|remove <IP>|purge|list}"
|
||||
exit 0
|
||||
esac
|
||||
@@ -7,6 +7,7 @@ abstract class Portal
|
||||
protected $error;
|
||||
|
||||
protected $AUTHORIZED_CLIENTS_FILE = "/tmp/EVILPORTAL_CLIENTS.txt";
|
||||
private $BASE_EP_COMMAND = 'module EvilPortal';
|
||||
|
||||
public function __construct($request)
|
||||
{
|
||||
@@ -24,44 +25,80 @@ abstract class Portal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command in the background and don't wait for it to finish.
|
||||
* @param $command: The command to run
|
||||
*/
|
||||
protected function execBackground($command)
|
||||
{
|
||||
exec("echo \"{$command}\" | at now");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an iptables rule allowing the client to access the internet and writes them to the authorized clients.
|
||||
* Override this method to add other authorization steps validation.
|
||||
* @param $clientIP: The IP address of the client to authorize
|
||||
* @return bool: True if the client was successfully authorized otherwise false.
|
||||
*/
|
||||
protected function authorizeClient($clientIP)
|
||||
{
|
||||
if (!$this->isClientAuthorized($clientIP)) {
|
||||
exec("iptables -t nat -I PREROUTING -s {$clientIP} -j ACCEPT");
|
||||
// exec("{$this->BASE_EP_COMMAND} add {$clientIP}");
|
||||
file_put_contents($this->AUTHORIZED_CLIENTS_FILE, "{$clientIP}\n", FILE_APPEND);
|
||||
$this->redirect();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client authorization here.
|
||||
* By default it just checks that the redirection target is in the request.
|
||||
* Override this to perform your own validation.
|
||||
*/
|
||||
protected function handleAuthorization()
|
||||
{
|
||||
if (isset($this->request->target)) {
|
||||
$this->authorizeClient($_SERVER['REMOTE_ADDR']);
|
||||
$this->showSuccess();
|
||||
$this->onSuccess();
|
||||
$this->redirect();
|
||||
} elseif ($this->isClientAuthorized($_SERVER['REMOTE_ADDR'])) {
|
||||
$this->showSuccess();
|
||||
$this->redirect();
|
||||
} else {
|
||||
$this->showError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to redirect to on successful authorization.
|
||||
*/
|
||||
protected function redirect()
|
||||
{
|
||||
header("Location: {$this->request->target}", true, 302);
|
||||
}
|
||||
|
||||
protected function showSuccess()
|
||||
/**
|
||||
* Override this to do something when the client is successfully authorized.
|
||||
* By default it just notifies the Web UI.
|
||||
*/
|
||||
protected function onSuccess()
|
||||
{
|
||||
echo "You have been authorized successfully.";
|
||||
$this->execBackground("notify New client authorized through EvilPortal!");
|
||||
}
|
||||
|
||||
/**
|
||||
* If an error occurs then do something here.
|
||||
* Override to provide your own functionality.
|
||||
*/
|
||||
protected function showError()
|
||||
{
|
||||
echo "You have not been authorized.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the client has been authorized.
|
||||
* @param $clientIP: The IP of the client to check.
|
||||
* @return bool|int: True if the client is authorized else false.
|
||||
*/
|
||||
protected function isClientAuthorized($clientIP)
|
||||
{
|
||||
$authorizeClients = file_get_contents($this->AUTHORIZED_CLIENTS_FILE);
|
||||
|
||||
4
EvilPortal/includes/skeleton/.disable
Normal file
4
EvilPortal/includes/skeleton/.disable
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Commands in this file are ran when a portal is de-activated.
|
||||
# You can use any interpreter you want to, the default is bash.
|
||||
4
EvilPortal/includes/skeleton/.enable
Normal file
4
EvilPortal/includes/skeleton/.enable
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Commands in this file are ran when a portal is activated and when Evil Portal startsup on boot.
|
||||
# You can use any interpreter you want to, the default is bash.
|
||||
@@ -5,18 +5,27 @@ class MyPortal extends Portal
|
||||
|
||||
public function handleAuthorization()
|
||||
{
|
||||
// handle form input or other extra things there
|
||||
|
||||
// Call parent to handle basic authorization first
|
||||
parent::handleAuthorization();
|
||||
|
||||
// Check for other form data here
|
||||
}
|
||||
|
||||
public function showSuccess()
|
||||
/**
|
||||
* Override this to do something when the client is successfully authorized.
|
||||
* By default it just notifies the Web UI.
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
// Calls default success message
|
||||
parent::showSuccess();
|
||||
parent::onSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* If an error occurs then do something here.
|
||||
* Override to provide your own functionality.
|
||||
*/
|
||||
public function showError()
|
||||
{
|
||||
// Calls default error message
|
||||
|
||||
45
EvilPortal/includes/skeleton/helper.php
Normal file
45
EvilPortal/includes/skeleton/helper.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* getClientMac
|
||||
* Gets the mac address of a client by the IP address
|
||||
* Returns the mac address as a string
|
||||
* @param $clientIP : The clients IP address
|
||||
* @return string
|
||||
*/
|
||||
function getClientMac($clientIP)
|
||||
{
|
||||
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $2}'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* getClientSSID
|
||||
* Gets the SSID a client is associated by the IP address
|
||||
* Returns the SSID as a string
|
||||
* @param $clientIP : The clients IP address
|
||||
* @return string
|
||||
*/
|
||||
function getClientSSID($clientIP)
|
||||
{
|
||||
// Get the clients mac address. We need this to get the SSID
|
||||
$mac = getClientMac($clientIP);
|
||||
|
||||
// get the path to the log file
|
||||
$pineAPLogPath = trim(file_get_contents('/etc/pineapple/pineap_log_location'));
|
||||
|
||||
// get the ssid
|
||||
return trim(exec("grep " . $mac . " " . $pineAPLogPath . "pineap.log | grep 'Association' | awk -F ',' '{print $4}'"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* getClientHostName
|
||||
* Gets the host name of the connected client by the IP address
|
||||
* Returns the host name as a string
|
||||
* @param $clientIP : The clients IP address
|
||||
* @return string
|
||||
*/
|
||||
function getClientHostName($clientIP)
|
||||
{
|
||||
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $4}'"));
|
||||
}
|
||||
@@ -1,27 +1,33 @@
|
||||
<?php
|
||||
$destination = "http://". $_SERVER['HTTP_HOST'] . $_SERVER['HTTP_URI'] . "";
|
||||
$destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['HTTP_URI'] . "";
|
||||
require_once('helper.php');
|
||||
?>
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Evil Portal</title>
|
||||
<script type="text/javascript">
|
||||
function redirect() { setTimeout(function(){window.location = "/captiveportal/index.php";},100);}
|
||||
</script>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</HEAD>
|
||||
|
||||
<BODY>
|
||||
<center>
|
||||
<div style="text-align: center;">
|
||||
<h1>Evil Portal</h1>
|
||||
<p>This is the default Evil Portal page</p>
|
||||
<p>This is the default Evil Portal page.</p>
|
||||
<p>The SSID you are connected to is <?=getClientSSID($_SERVER['REMOTE_ADDR']);?></p>
|
||||
<p>Your host name is <?=getClientHostName($_SERVER['REMOTE_ADDR']);?></p>
|
||||
<p>Your MAC Address is <?=getClientMac($_SERVER['REMOTE_ADDR']);?></p>
|
||||
<p>Your internal IP address is <?=$_SERVER['REMOTE_ADDR'];?></p>
|
||||
|
||||
<form method="POST" action="/captiveportal/index.php" onsubmit="redirect()">
|
||||
<form method="POST" action="/captiveportal/index.php">
|
||||
<input type="hidden" name="target" value="<?=$destination?>">
|
||||
<button type="submit">Authorize</button>
|
||||
</form>
|
||||
|
||||
</center>
|
||||
</div>
|
||||
|
||||
</BODY>
|
||||
|
||||
</HTML>
|
||||
</HTML>
|
||||
|
||||
4
EvilPortal/includes/skeleton/portalinfo.json
Normal file
4
EvilPortal/includes/skeleton/portalinfo.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": null,
|
||||
"type": "basic"
|
||||
}
|
||||
4
EvilPortal/includes/targeted_skeleton/.disable
Normal file
4
EvilPortal/includes/targeted_skeleton/.disable
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Commands in this file are ran when a portal is de-activated.
|
||||
# You can use any interpreter you want to, the default is bash.
|
||||
4
EvilPortal/includes/targeted_skeleton/.enable
Normal file
4
EvilPortal/includes/targeted_skeleton/.enable
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Commands in this file are ran when a portal is activated and when Evil Portal startsup on boot.
|
||||
# You can use any interpreter you want to, the default is bash.
|
||||
33
EvilPortal/includes/targeted_skeleton/MyPortal.php
Normal file
33
EvilPortal/includes/targeted_skeleton/MyPortal.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php namespace evilportal;
|
||||
|
||||
class MyPortal extends Portal
|
||||
{
|
||||
|
||||
public function handleAuthorization()
|
||||
{
|
||||
// handle form input or other extra things there
|
||||
|
||||
// Call parent to handle basic authorization first
|
||||
parent::handleAuthorization();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to do something when the client is successfully authorized.
|
||||
* By default it just notifies the Web UI.
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
// Calls default success message
|
||||
parent::onSuccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* If an error occurs then do something here.
|
||||
* Override to provide your own functionality.
|
||||
*/
|
||||
public function showError()
|
||||
{
|
||||
// Calls default error message
|
||||
parent::showError();
|
||||
}
|
||||
}
|
||||
35
EvilPortal/includes/targeted_skeleton/default.php
Normal file
35
EvilPortal/includes/targeted_skeleton/default.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
$destination = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['HTTP_URI'] . "";
|
||||
require_once('helper.php');
|
||||
?>
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>Evil Portal</title>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<script type="text/javascript">
|
||||
function redirect() { setTimeout(function(){window.location = "/captiveportal/index.php";},100);}
|
||||
</script>
|
||||
</HEAD>
|
||||
|
||||
<BODY>
|
||||
<div style="text-align: center;">
|
||||
<h1>Evil Portal</h1>
|
||||
<p>This is the default Evil Portal page.</p>
|
||||
<p>The SSID you are connected to is <?=getClientSSID($_SERVER['REMOTE_ADDR']);?></p>
|
||||
<p>Your host name is <?=getClientHostName($_SERVER['REMOTE_ADDR']);?></p>
|
||||
<p>Your MAC Address is <?=getClientMac($_SERVER['REMOTE_ADDR']);?></p>
|
||||
<p>Your internal IP address is <?=$_SERVER['REMOTE_ADDR'];?></p>
|
||||
|
||||
<form method="POST" action="/captiveportal/index.php" onsubmit="redirect()">
|
||||
<input type="hidden" name="target" value="<?=$destination?>">
|
||||
<button type="submit">Authorize</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</BODY>
|
||||
|
||||
</HTML>
|
||||
45
EvilPortal/includes/targeted_skeleton/helper.php
Normal file
45
EvilPortal/includes/targeted_skeleton/helper.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* getClientMac
|
||||
* Gets the mac address of a client by the IP address
|
||||
* Returns the mac address as a string
|
||||
* @param $clientIP : The clients IP address
|
||||
* @return string
|
||||
*/
|
||||
function getClientMac($clientIP)
|
||||
{
|
||||
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $2}'"));
|
||||
}
|
||||
|
||||
/**
|
||||
* getClientSSID
|
||||
* Gets the SSID a client is associated by the IP address
|
||||
* Returns the SSID as a string
|
||||
* @param $clientIP : The clients IP address
|
||||
* @return string
|
||||
*/
|
||||
function getClientSSID($clientIP)
|
||||
{
|
||||
// Get the clients mac address. We need this to get the SSID
|
||||
$mac = getClientMac($clientIP);
|
||||
|
||||
// get the path to the log file
|
||||
$pineAPLogPath = trim(file_get_contents('/etc/pineapple/pineap_log_location'));
|
||||
|
||||
// get the ssid
|
||||
return trim(exec("grep " . $mac . " " . $pineAPLogPath . "pineap.log | grep 'Association' | awk -F ',' '{print $4}'"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* getClientHostName
|
||||
* Gets the host name of the connected client by the IP address
|
||||
* Returns the host name as a string
|
||||
* @param $clientIP : The clients IP address
|
||||
* @return string
|
||||
*/
|
||||
function getClientHostName($clientIP)
|
||||
{
|
||||
return trim(exec("grep " . escapeshellarg($clientIP) . " /tmp/dhcp.leases | awk '{print $4}'"));
|
||||
}
|
||||
81
EvilPortal/includes/targeted_skeleton/index.php
Normal file
81
EvilPortal/includes/targeted_skeleton/index.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
require_once('helper.php');
|
||||
|
||||
/**
|
||||
*
|
||||
* DO NOT MODIFY THIS FILE
|
||||
* I highly recommend against modifying this file unless you know what you are doing!
|
||||
*
|
||||
* This file handles determining the destination file a client should see based on the conditions set in the json.
|
||||
*
|
||||
*/
|
||||
|
||||
// The value for this variable needs to be set when an new instance of a portal is created
|
||||
// EvilPortal does this automatically when a targeted portal is created by running:
|
||||
// sed -i 's/"portal_name_here"/"{portalName}"/g' index.php
|
||||
$PORTAL_NAME = "portal_name_here";
|
||||
|
||||
// Get the information about the client and map
|
||||
// it to the rule key specified in the {$PORTAL_NAME}.ep file
|
||||
$MAPPED_RULES = [
|
||||
"mac" => getClientMac($_SERVER['REMOTE_ADDR']),
|
||||
"ssid" => getClientSSID($_SERVER['REMOTE_ADDR']),
|
||||
"hostname" => getClientHostName($_SERVER['REMOTE_ADDR']),
|
||||
"useragent" => $_SERVER['HTTP_USER_AGENT']
|
||||
];
|
||||
|
||||
// Read the json
|
||||
$jsonData = json_decode(file_get_contents("{$PORTAL_NAME}.ep"), true);
|
||||
$routeData = $jsonData['targeted_rules'];
|
||||
|
||||
// This variable represents the page to include
|
||||
$includePage = null;
|
||||
|
||||
// Check rules to find the page
|
||||
foreach ($routeData['rule_order'] as $key) {
|
||||
$includePage = handle_rule($routeData['rules'][$key], $MAPPED_RULES[$key]);
|
||||
if ($includePage != null) {
|
||||
include $includePage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We have to display something.
|
||||
// If the includePage variable is still null after checking the rules
|
||||
// then include the default page.
|
||||
if ($includePage == null) {
|
||||
include $routeData['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given rule matches a given value
|
||||
* @param $rules: The rules to check the client data against
|
||||
* @param $client_data: The data to check if the rules match
|
||||
* @return string: If a rule matches it returns the page to include, null otherwise
|
||||
*/
|
||||
function handle_rule($rules, $client_data) {
|
||||
$return_value = null;
|
||||
foreach ($rules as $key => $val) {
|
||||
switch($key) {
|
||||
case "exact": // exact matches
|
||||
if (isset($val[$client_data])) {
|
||||
$return_value = $val[$client_data];
|
||||
break 2; // break out of the loop
|
||||
}
|
||||
break 1;
|
||||
|
||||
case "regex": // regex matches
|
||||
foreach($val as $expression => $destination) {
|
||||
if (preg_match($expression, $client_data)) {
|
||||
$return_value = $destination;
|
||||
break 1; // match was found. Exit this loop
|
||||
}
|
||||
|
||||
if ($return_value != null)
|
||||
break 2; // break out of the main loop
|
||||
}
|
||||
break 1;
|
||||
}
|
||||
}
|
||||
return $return_value;
|
||||
}
|
||||
4
EvilPortal/includes/targeted_skeleton/jquery-2.2.1.min.js
vendored
Normal file
4
EvilPortal/includes/targeted_skeleton/jquery-2.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
34
EvilPortal/includes/targeted_skeleton/portalinfo.json
Normal file
34
EvilPortal/includes/targeted_skeleton/portalinfo.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": null,
|
||||
"type": "targeted",
|
||||
"targeted_rules": {
|
||||
"default": "default.php",
|
||||
"rule_order": ["mac", "ssid", "hostname", "useragent"],
|
||||
"rules": {
|
||||
"mac": {
|
||||
"exact": {
|
||||
},
|
||||
"regex": {
|
||||
}
|
||||
},
|
||||
"ssid": {
|
||||
"exact": {
|
||||
},
|
||||
"regex": {
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"exact": {
|
||||
},
|
||||
"regex": {
|
||||
}
|
||||
},
|
||||
"useragent": {
|
||||
"exact": {
|
||||
},
|
||||
"regex": {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,322 +1,671 @@
|
||||
registerController("EvilPortalController", ['$api', '$scope', function ($api, $scope) {
|
||||
|
||||
getControls();
|
||||
getPortals();
|
||||
|
||||
$scope.portals = [];
|
||||
$scope.portalToDelete = null;
|
||||
$scope.portalDeleteValidation = '';
|
||||
$scope.messages = [];
|
||||
$scope.newPortalName = '';
|
||||
$scope.throbber = true;
|
||||
$scope.running = false;
|
||||
$scope.library = true;
|
||||
$scope.whiteList = '';
|
||||
$scope.whiteListInput = '';
|
||||
$scope.accessList = '';
|
||||
$scope.accessListInput = '';
|
||||
$scope.workshopPortal = {name: "", files: [], storage: "internal"};
|
||||
$scope.editPortalFile = {portalName: "", storage: "", file: "", code: ""};
|
||||
|
||||
$scope.handleControl = function (control) {
|
||||
control.throbber = true;
|
||||
switch (control.title) {
|
||||
|
||||
case "CaptivePortal":
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "startStop"
|
||||
}, function (response) {
|
||||
getControls();
|
||||
control.throbber = false;
|
||||
if (!response.control_success) {
|
||||
$scope.sendMessage(control.title, response.control_message);
|
||||
}
|
||||
$scope.refreshLivePreview()
|
||||
});
|
||||
break;
|
||||
|
||||
case "Auto Start":
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "enableDisable"
|
||||
}, function (response) {
|
||||
getControls();
|
||||
control.throbber = false;
|
||||
if (!response.control_success) {
|
||||
$scope.sendMessage(control.title, response.control_message);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
// status information about the module
|
||||
$scope.evilPortal = {
|
||||
"throbber": false,
|
||||
"sdAvailable": false,
|
||||
"running": false,
|
||||
"startOnBoot": false,
|
||||
"library": true
|
||||
};
|
||||
|
||||
// controls that belong in the Controls pane
|
||||
$scope.controls = [
|
||||
{ "title": "Captive Portal", "visible": true, "throbber": false, "status": "Start"},
|
||||
{"title": "Start On Boot", "visible": true, "throbber": false, "status": "Enable"}
|
||||
];
|
||||
|
||||
// messages to be displayed in the Messages pane
|
||||
$scope.messages = [];
|
||||
|
||||
$scope.whiteList = {"clients": "", "toManipulate": null};
|
||||
|
||||
$scope.accessList = {"clients": "", "toManipulate": null};
|
||||
|
||||
// all of the portals that could be found
|
||||
$scope.portals = [];
|
||||
|
||||
// a model of a new portal to create
|
||||
$scope.newPortal = {"type": "basic", "name": ""};
|
||||
|
||||
// deleting portal stuff
|
||||
$scope.portalToDelete = null;
|
||||
$scope.portalDeleteValidation = null;
|
||||
|
||||
// the portal workshop
|
||||
$scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true},
|
||||
"onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the workshop object to a blank slate with initial values.
|
||||
*/
|
||||
$scope.resetWorkshop = function () {
|
||||
$scope.workshop = {"portal": {}, "dirContents": null, "inRoot": true, "rootDirectory": null, "editFile": {"path": null, "isNewFile": true},
|
||||
"onEnable": null, "onDisable": null, "concreteTargetedRules": null, "workingTargetedRules": null, "deleteFile": null
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Push a message to the Evil Portal Messages Pane
|
||||
* @param t: The Title of the message
|
||||
* @param m: The message body
|
||||
*/
|
||||
$scope.sendMessage = function (t, m) {
|
||||
// Add a new message to the top of the list
|
||||
$scope.messages.unshift({title: t, msg: m});
|
||||
|
||||
// if there are 4 items in the list remove the 4th item
|
||||
if ($scope.messages.length == 4) {
|
||||
if ($scope.messages.length === 4) {
|
||||
$scope.dismissMessage(3);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a message from the Evil Portal Messages pane
|
||||
* @param $index: The index of the message in the list to remove
|
||||
*/
|
||||
$scope.dismissMessage = function ($index) {
|
||||
//var index = $scope.messages.indexOf(message);
|
||||
$scope.messages.splice($index, 1);
|
||||
};
|
||||
|
||||
function getControls() {
|
||||
$scope.throbber = true;
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "getControlValues"
|
||||
}, function (response) {
|
||||
updateControls(response);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Preform an action for a given control
|
||||
* This can be starting the captive portal or toggle on boot.
|
||||
* @param control: The control to handle
|
||||
*/
|
||||
$scope.handleControl = function(control) {
|
||||
control.throbber = true;
|
||||
var actionToPreform = null;
|
||||
switch(control.title) {
|
||||
case "Captive Portal":
|
||||
actionToPreform = "toggleCaptivePortal";
|
||||
break;
|
||||
|
||||
function updateControls(response) {
|
||||
var running;
|
||||
var autostart;
|
||||
if (response.running == false) {
|
||||
running = "Start";
|
||||
$scope.running = false;
|
||||
} else {
|
||||
running = "Stop";
|
||||
$scope.running = true;
|
||||
case "Start On Boot":
|
||||
actionToPreform = "toggleOnBoot";
|
||||
break;
|
||||
}
|
||||
if (response.autostart == false) {
|
||||
autostart = "Enable";
|
||||
} else {
|
||||
autostart = "Disable";
|
||||
}
|
||||
$scope.controls = [
|
||||
{
|
||||
title: "CaptivePortal",
|
||||
status: running,
|
||||
visible: true,
|
||||
throbber: false
|
||||
},
|
||||
{
|
||||
title: "Auto Start",
|
||||
status: autostart,
|
||||
visible: true,
|
||||
throbber: false
|
||||
}];
|
||||
$scope.throbber = false;
|
||||
}
|
||||
|
||||
$scope.createNewPortal = function () {
|
||||
if (actionToPreform !== null) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: actionToPreform
|
||||
}, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage(control.title, response.message);
|
||||
}
|
||||
getStatus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the information in the newPortal model and then makes an API request to create a new portal.
|
||||
* @param storage: The storage medium to create the portal on (internal or sd)
|
||||
*/
|
||||
$scope.createNewPortal = function(storage) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "createNewPortal",
|
||||
portalName: $scope.newPortalName
|
||||
}, function (response) {
|
||||
if (response.create_success) {
|
||||
getPortals();
|
||||
$scope.newPortalName = '';
|
||||
} else {
|
||||
$scope.sendMessage("Error Creating Portal", response.create_message);
|
||||
name: $scope.newPortal.name,
|
||||
type: $scope.newPortal.type,
|
||||
storage: storage
|
||||
}, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage('Error Creating Portal', response.message);
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deletePortalRequest = function(portal) {
|
||||
$scope.portalToDelete = portal;
|
||||
console.log(portal);
|
||||
};
|
||||
|
||||
$scope.deletePortal = function (portal) {
|
||||
console.log(portal.storage);
|
||||
console.log(portal.title);
|
||||
$scope.portalToDelete = null;
|
||||
$scope.portalDeleteValidation = null
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "deletePortal",
|
||||
storage: portal.storage,
|
||||
name: portal.title
|
||||
}, function (response) {
|
||||
$scope.sendMessage("Delete Portal", response.message);
|
||||
$scope.newPortal = {"type": "basic", "name": ""};
|
||||
getPortals();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.activatePortal = function (portal) {
|
||||
/**
|
||||
* Move a given portal between storage mediums if an SD card is present.
|
||||
* @param portal: The portal to move
|
||||
*/
|
||||
$scope.movePortal = function(portal) {
|
||||
if (!$scope.evilPortal.sdAvailable) {
|
||||
$scope.sendMessage("No SD Card.", "An SD card must be present to preform this action.");
|
||||
return;
|
||||
}
|
||||
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "movePortal",
|
||||
name: portal.title,
|
||||
storage: portal.storage
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
getPortals();
|
||||
$scope.sendMessage("Moved Portal", response.message);
|
||||
} else {
|
||||
$scope.sendMessage("Error Moving " + portal.title, response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a portal from the wifi pineapple
|
||||
* @param verified: Has the delete request been verified? If so then make the API request otherwise setup
|
||||
* @param portal: The portal to delete
|
||||
*/
|
||||
$scope.deletePortal = function(verified, portal) {
|
||||
if (!verified) { // if the request has not been verified then setup the shits
|
||||
$scope.portalToDelete = portal;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.portalToDelete === null || $scope.portalToDelete.fullPath === null) {
|
||||
$scope.sendMessage("Unable To Delete Portal", "No portal was set for deletion.");
|
||||
return;
|
||||
}
|
||||
deleteFileOrDirectory($scope.portalToDelete.fullPath, function (response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error Deleting Portal", response.message); // push an error if deletion failed
|
||||
} else {
|
||||
$scope.sendMessage("Deleted Portal", "Successfully deleted " + $scope.portalToDelete.title + ".");
|
||||
$scope.portalToDelete = null;
|
||||
$scope.portalDeleteValidation = null;
|
||||
getPortals(); // refresh the library
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate a portal
|
||||
* @param portal: The portal to activate
|
||||
*/
|
||||
$scope.activatePortal = function(portal) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "activatePortal",
|
||||
storage: portal.storage,
|
||||
name: portal.title
|
||||
}, function (response) {
|
||||
//$scope.sendMessage("Activate Portal", response.message);
|
||||
getPortals();
|
||||
name: portal.title,
|
||||
storage: portal.storage
|
||||
}, function(response) {
|
||||
console.log(response);
|
||||
if (response.success) {
|
||||
getPortals();
|
||||
$scope.sendMessage("Activated Portal", portal.title + " has been activated successfully.");
|
||||
} else {
|
||||
$scope.sendMessage("Error Activating " + portal.title, response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deactivatePortal = function (portal) {
|
||||
/**
|
||||
* Deactivate a given portal if its active
|
||||
* @param portal: The portal to deactivate
|
||||
*/
|
||||
$scope.deactivatePortal = function(portal) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "deactivatePortal",
|
||||
storage: portal.storage,
|
||||
name: portal.title
|
||||
}, function (response) {
|
||||
//$scope.sendMessage("Deactivate Portal", response.message);
|
||||
getPortals();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editPortal = function (portal, file) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "getPortalCode",
|
||||
storage: portal.storage,
|
||||
name: portal.name,
|
||||
portalFile: file
|
||||
}, function (response) {
|
||||
//$scope.sendMessage("Edit Portal", response.message);
|
||||
$scope.editPortalFile.code = response.code;
|
||||
$scope.editPortalFile.file = file;
|
||||
$scope.editPortalFile.portalName = portal.name;
|
||||
$scope.editPortalFile.storage = portal.storage;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.savePortalCode = function (editFile) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "submitPortalCode",
|
||||
storage: editFile.storage,
|
||||
portalCode: editFile.code,
|
||||
name: editFile.portalName,
|
||||
fileName: editFile.file
|
||||
}, function (response) {
|
||||
$scope.sendMessage("Edit File", response.message);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getPortalFiles = function (portal) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "portalFiles",
|
||||
storage: portal.storage,
|
||||
name: portal.title
|
||||
}, function (response) {
|
||||
$scope.workshopPortal.name = portal.title;
|
||||
$scope.workshopPortal.storage = portal.storage;
|
||||
$scope.workshopPortal.files = response.portalFiles;
|
||||
$scope.library = false;
|
||||
});
|
||||
};
|
||||
|
||||
function getPortals() {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "portalList"
|
||||
}, function (response) {
|
||||
$scope.portals = [];
|
||||
for (var i = 0; i < response.length; i++) {
|
||||
$scope.portals.unshift({
|
||||
title: response[i].title,
|
||||
storage: response[i].location,
|
||||
active: response[i].active
|
||||
});
|
||||
//console.log({title: response[i].title, storage: response[i].location, active: response[i].active});
|
||||
name: portal.title,
|
||||
storage: portal.storage
|
||||
}, function(response) {
|
||||
console.log(response);
|
||||
if (response.success) {
|
||||
getPortals();
|
||||
$scope.sendMessage("Deactivated Portal", portal.title + " has been deactivated successfully.");
|
||||
} else {
|
||||
$scope.sendMessage("Error Deactivating " + portal.title, response.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.refreshLivePreview = function () {
|
||||
window.frames['livePreviewIframe'].src = "http://172.16.42.1";
|
||||
};
|
||||
|
||||
$scope.getList = function (listToGet) {
|
||||
/**
|
||||
* Load portal contents and open it up in the work bench
|
||||
* @param portal: The portal to get the contents of
|
||||
*/
|
||||
$scope.loadPortal = function (portal) {
|
||||
getFileOrDirectoryContent(portal.fullPath, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error Getting Contents", response.message);
|
||||
return;
|
||||
}
|
||||
$scope.workshop.inRoot = true;
|
||||
$scope.workshop.portal = portal;
|
||||
$scope.workshop.dirContents = response.content;
|
||||
$scope.workshop.rootDirectory = portal.fullPath;
|
||||
$scope.evilPortal.library = false;
|
||||
console.log(response.content);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load toggle commands for the current portal in the work bench.
|
||||
* These are the commands that are executed when a portal is enabled/disabled.
|
||||
*/
|
||||
$scope.loadToggleCommands = function() {
|
||||
[".enable", ".disable"].forEach(getScript);
|
||||
function getScript(scriptName) {
|
||||
getFileOrDirectoryContent($scope.workshop.rootDirectory + "/" + scriptName, function (response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error Getting Contents", response.message);
|
||||
return;
|
||||
}
|
||||
if (scriptName === ".enable")
|
||||
$scope.workshop.onEnable = response.content.fileContent;
|
||||
else
|
||||
$scope.workshop.onDisable = response.content.fileContent;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Save toggle commands.
|
||||
* @param cmdFile: The commands to save (enable, disable)
|
||||
*/
|
||||
$scope.saveToggleCommands = function(cmdFile) {
|
||||
function sendData(f, content) {
|
||||
writeToFile($scope.workshop.rootDirectory + "/" + f, content, false, function (response) {
|
||||
if (!response.success)
|
||||
$scope.sendMessage("Error write to file " + f, response.message);
|
||||
});
|
||||
}
|
||||
|
||||
switch(cmdFile) {
|
||||
case "disable":
|
||||
sendData(".disable", $scope.workshop.onDisable);
|
||||
break;
|
||||
|
||||
case "enable":
|
||||
sendData(".enable", $scope.workshop.onEnable);
|
||||
break;
|
||||
|
||||
default:
|
||||
sendData(".enable", $scope.workshop.onEnable);
|
||||
sendData(".disable", $scope.workshop.onDisable);
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the rules for targeted portals
|
||||
*/
|
||||
$scope.loadTargetedRules = function() {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "getList",
|
||||
listName: listToGet
|
||||
}, function (response) {
|
||||
if (response.list_success) {
|
||||
if (listToGet == "whiteList") {
|
||||
$scope.whiteList = response.list_contents;
|
||||
} else if (listToGet == "accessList") {
|
||||
$scope.accessList = response.list_contents;
|
||||
action: "getRules",
|
||||
name: $scope.workshop.portal.title,
|
||||
storage: $scope.workshop.portal.storage
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
$scope.workshop.concreteTargetedRules = response.data;
|
||||
$scope.workshop.workingTargetedRules = {"rules": {}};
|
||||
|
||||
// welcome to the realm of loops. I will be your guide
|
||||
// We have to turn each rule into a keyed set of rules with a rule index represented by var index
|
||||
// this is because we need a constant key for each rule when editing on the web interface
|
||||
// the index must be removed later before saving the results to the routes.json file
|
||||
// if you have a better way to do this you are my hero. Email me n3rdcav3@gmail.com or fork the repo :)
|
||||
|
||||
// This first loop loops over each rule categories such as "mac", "ssid" and so on
|
||||
for (var key in response.data['rules']) {
|
||||
|
||||
// we then create the a object with that key name in our workingData object
|
||||
$scope.workshop.workingTargetedRules['rules'][key] = {};
|
||||
|
||||
// Now its time to loop over each category specifier such as "exact" and "regex"
|
||||
for (var specifier in response.data['rules'][key]) {
|
||||
var index = 0;
|
||||
|
||||
// We then create that specifier in our workingData
|
||||
$scope.workshop.workingTargetedRules['rules'][key][specifier] = {};
|
||||
|
||||
// finally we loop over the specific rules defined in the specifier
|
||||
for (var r in response.data['rules'][key][specifier]) {
|
||||
var obj = {};
|
||||
obj['key'] = r;
|
||||
obj['destination'] = response.data['rules'][key][specifier][r];
|
||||
$scope.workshop.workingTargetedRules['rules'][key][specifier][index] = obj;
|
||||
}
|
||||
// increment index
|
||||
index++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$scope.sendMessage("List Data Error", response.list_message);
|
||||
console.log(response);
|
||||
$scope.sendMessage("Error", "There was an issue getting the portal rules.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addWhiteListClient = function () {
|
||||
/**
|
||||
* Remove a targeted rule
|
||||
* @param rule
|
||||
* @param specifier
|
||||
* @param index
|
||||
*/
|
||||
$scope.removeTargetedRule = function(rule, specifier, index) {
|
||||
delete $scope.workshop.workingTargetedRules['rules'][rule][specifier][index];
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new targeted rule
|
||||
* @param rule
|
||||
* @param specifier
|
||||
*/
|
||||
$scope.newTargetedRule = function(rule, specifier) {
|
||||
// make sure the specifier is set
|
||||
if ($scope.workshop.workingTargetedRules['rules'][rule][specifier] == undefined) {
|
||||
$scope.workshop.workingTargetedRules['rules'][rule][specifier] = {};
|
||||
}
|
||||
|
||||
var highest = 0;
|
||||
|
||||
// get the highest index
|
||||
for (var i in $scope.workshop.workingTargetedRules['rules'][rule][specifier]) {
|
||||
if (parseInt(i) >= highest) {
|
||||
highest = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.workshop.workingTargetedRules['rules'][rule][specifier][highest] = {"": ""};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the targeted rules and sned them to the API for saving
|
||||
*/
|
||||
$scope.saveTargetedRules = function() {
|
||||
// build the rules
|
||||
for (var key in $scope.workshop.concreteTargetedRules.rules) {
|
||||
for (var specifier in $scope.workshop.concreteTargetedRules.rules[key]) {
|
||||
var obj = {};
|
||||
for (var i in $scope.workshop.workingTargetedRules['rules'][key][specifier]) {
|
||||
obj[$scope.workshop.workingTargetedRules['rules'][key][specifier][i]['key']] = $scope.workshop.workingTargetedRules['rules'][key][specifier][i]['destination'];
|
||||
}
|
||||
$scope.workshop.concreteTargetedRules['rules'][key][specifier] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify($scope.workshop.concreteTargetedRules));
|
||||
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "addToList",
|
||||
listName: "whiteList",
|
||||
clientIP: $scope.whiteListInput
|
||||
}, function (response) {
|
||||
if (response.add_success) {
|
||||
$scope.whiteListInput = '';
|
||||
$scope.getList("whiteList");
|
||||
} else {
|
||||
$scope.sendMessage("White List", response.add_message);
|
||||
console.log(response);
|
||||
action: "saveRules",
|
||||
name: $scope.workshop.portal.title,
|
||||
storage: $scope.workshop.portal.storage,
|
||||
rules: JSON.stringify($scope.workshop.concreteTargetedRules)
|
||||
}, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error", response.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeWhiteListClient = function () {
|
||||
/**
|
||||
* check if a given object is empty.
|
||||
* @param obj: The object to check
|
||||
* @returns {boolean}: true if empty false if not empty
|
||||
*/
|
||||
$scope.isObjectEmpty = function(obj) {
|
||||
return (Object.keys(obj).length === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the contents of a given file.
|
||||
* @param filePath: The path to the file to load
|
||||
*/
|
||||
$scope.loadFileContent = function(filePath) {
|
||||
getFileOrDirectoryContent(filePath, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error Getting Contents", response.message);
|
||||
return;
|
||||
}
|
||||
$scope.workshop.editFile = {
|
||||
"name": response.content.name,
|
||||
"path": response.content.path,
|
||||
"size": response.content.size,
|
||||
"content": response.content.fileContent
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup the workshop to create a new empty file.
|
||||
*/
|
||||
$scope.setupNewFile = function() {
|
||||
var basePath = ($scope.workshop.portal.storage === "sd") ? "/sd/portals/" : "/root/portals/";
|
||||
$scope.workshop.editFile.path = basePath + $scope.workshop.portal.title + "/";
|
||||
$scope.workshop.editFile.isNewFile = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Write file content to the file system.
|
||||
* @param editFile: A portal.editFile object
|
||||
*/
|
||||
$scope.saveFileContent = function(editFile) {
|
||||
// new files wont have the filename in the path so make sure to set it here if needed.
|
||||
if (!editFile.path.includes(editFile.name))
|
||||
editFile.path = editFile.path + editFile.name;
|
||||
|
||||
console.log(editFile.path);
|
||||
writeToFile(editFile.path, editFile.content, false, function(response) {
|
||||
if (!response.success)
|
||||
$scope.sendMessage("Error write to file " + editFile.name, response.message);
|
||||
|
||||
$scope.loadPortal($scope.workshop.portal); // refresh the portal
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a requested file.
|
||||
*/
|
||||
$scope.deleteFile = function() {
|
||||
deleteFileOrDirectory($scope.workshop.deleteFile.path, function(response){
|
||||
if (!response.success)
|
||||
$scope.sendMessage("Error deleting file " + $scope.workshop.deleteFile.name, response.message);
|
||||
|
||||
$scope.loadPortal($scope.workshop.portal); // refresh the portal
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load either the white list or the authorized clients (access) list
|
||||
* @param listName: The name of the list: whiteList or accessList (authorized clients)
|
||||
*/
|
||||
$scope.getList = function (listName) {
|
||||
var whiteList = '/pineapple/modules/EvilPortal/data/allowed.txt';
|
||||
var authorized = '/tmp/EVILPORTAL_CLIENTS.txt';
|
||||
|
||||
getFileOrDirectoryContent((listName === "whiteList") ? whiteList : authorized, function (response) {
|
||||
switch (listName) {
|
||||
case 'whiteList':
|
||||
$scope.whiteList.clients = response.content.fileContent;
|
||||
break;
|
||||
case 'accessList':
|
||||
$scope.accessList.clients = response.content.fileContent;
|
||||
break;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a client from either the white list (whiteList) the authorized clients list (accessList)
|
||||
* @param listName: whiteList or accessList
|
||||
*/
|
||||
$scope.removeClientFromList = function(listName) {
|
||||
var clientToRemove = (listName === 'whiteList') ? $scope.whiteList.toManipulate : $scope.accessList.toManipulate;
|
||||
console.log(clientToRemove);
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "removeFromList",
|
||||
listName: "whiteList",
|
||||
clientIP: $scope.whiteListInput
|
||||
}, function (response) {
|
||||
if (response.remove_success) {
|
||||
$scope.whiteListInput = '';
|
||||
$scope.getList("whiteList");
|
||||
} else {
|
||||
$scope.sendMessage("White List", response.remove_message);
|
||||
console.log(response);
|
||||
action: "removeClientFromList",
|
||||
clientIP: clientToRemove,
|
||||
listName: listName
|
||||
}, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error", response.message);
|
||||
return;
|
||||
}
|
||||
$scope.getList(listName);
|
||||
switch (listName) {
|
||||
case 'whiteList':
|
||||
$scope.whiteList.toManipulate = null;
|
||||
break;
|
||||
case 'accessList':
|
||||
$scope.accessList.toManipulate = null;
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new client to the white list
|
||||
*/
|
||||
$scope.addWhiteListClient = function() {
|
||||
writeToFile('/pineapple/modules/EvilPortal/data/allowed.txt', $scope.whiteList.toManipulate + "\n", true, function(response) {
|
||||
$scope.getList('whiteList');
|
||||
});
|
||||
$scope.whiteList.toManipulate = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Authorize a new client
|
||||
*/
|
||||
$scope.authorizeClient = function () {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "addToList",
|
||||
listName: "accessList",
|
||||
clientIP: $scope.accessListInput
|
||||
action: "authorizeClient",
|
||||
clientIP: $scope.accessList.toManipulate
|
||||
}, function (response) {
|
||||
if (response.add_success) {
|
||||
$scope.accessListInput = '';
|
||||
$scope.getList("accessList");
|
||||
} else {
|
||||
$scope.sendMessage("Access List", response.add_message);
|
||||
console.log(response);
|
||||
}
|
||||
$scope.getList('accessList');
|
||||
$scope.accessList.toManipulate = null;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.revokeClient = function () {
|
||||
/**
|
||||
* Get a line clicked in a text area and set that line as the text for a text input
|
||||
* @param textareaId: The id of the text area to grab from
|
||||
* @param inputname: The name of the input field to write to
|
||||
*/
|
||||
$scope.getClickedClient = function(textareaId, inputname) {
|
||||
var textarea = $('#' + textareaId);
|
||||
var lineNumber = textarea.val().substr(0, textarea[0].selectionStart).split('\n').length;
|
||||
var ssid = textarea.val().split('\n')[lineNumber-1].trim();
|
||||
$("input[name='" + inputname + "']").val(ssid).trigger('input');
|
||||
};
|
||||
|
||||
/**
|
||||
* Write given content to a given file on the file system.
|
||||
* @param filePath: The path to the file to write content to
|
||||
* @param fileContent: The content to write to the file
|
||||
* @param appendFile: Should the content be append to the file (true) or overwrite the file (false)
|
||||
* @param callback: A callback function to handle the API response
|
||||
*/
|
||||
function writeToFile(filePath, fileContent, appendFile, callback) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "removeFromList",
|
||||
listName: "accessList",
|
||||
clientIP: $scope.accessListInput
|
||||
}, function (response) {
|
||||
if (response.remove_success) {
|
||||
$scope.accessListInput = '';
|
||||
$scope.getList("accessList");
|
||||
} else {
|
||||
$scope.sendMessage("Access List", response.remove_message);
|
||||
console.log(response);
|
||||
}
|
||||
action: "writeFileContent",
|
||||
filePath: filePath,
|
||||
content: fileContent,
|
||||
append: appendFile
|
||||
}, function(response) {
|
||||
callback(response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a directory
|
||||
* @param pathToObject: The full path to the file or directory to get the contents of
|
||||
* @param callback: A function that handles the response from the API.
|
||||
*/
|
||||
function getFileOrDirectoryContent(pathToObject, callback) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "getFileContent",
|
||||
filePath: pathToObject
|
||||
}, function(response) {
|
||||
callback(response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file or directory from the pineapples filesystem.
|
||||
* This is intended to be used for only deleting portals and portal related files but anything can be delete.
|
||||
* @param fileOrDirectory: The path to the file to delete
|
||||
* @param callback: The callback function to handle the API response
|
||||
*/
|
||||
function deleteFileOrDirectory(fileOrDirectory, callback) {
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "deleteFile",
|
||||
filePath: fileOrDirectory
|
||||
}, function(response) {
|
||||
callback(response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the control models so they reflect the proper information
|
||||
*/
|
||||
function updateControls() {
|
||||
$scope.controls = [
|
||||
{
|
||||
"title": "Captive Portal",
|
||||
"status": ($scope.evilPortal.running) ? "Stop" : "Start",
|
||||
"visible": true,
|
||||
"throbber": false
|
||||
},
|
||||
{
|
||||
"title": "Start On Boot",
|
||||
"status": ($scope.evilPortal.startOnBoot) ? "Disable": "Enable",
|
||||
"visible": true,
|
||||
"throbber": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status's for the controls in the Controls pane and other various information
|
||||
*/
|
||||
function getStatus() {
|
||||
$scope.evilPortal.throbber = true;
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "status"
|
||||
}, function (response) {
|
||||
for (var key in response) {
|
||||
if (response.hasOwnProperty(key) && $scope.evilPortal.hasOwnProperty(key)) {
|
||||
$scope.evilPortal[key] = response[key];
|
||||
}
|
||||
}
|
||||
$scope.evilPortal.throbber = false;
|
||||
updateControls();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the portals on the Pineapple
|
||||
*/
|
||||
function getPortals() {
|
||||
$scope.evilPortal.throbber = true;
|
||||
$api.request({
|
||||
module: "EvilPortal",
|
||||
action: "listAvailablePortals"
|
||||
}, function(response) {
|
||||
if (!response.success) {
|
||||
$scope.sendMessage("Error Listing Portals", "An error occurred while trying to get list of portals.");
|
||||
return;
|
||||
}
|
||||
$scope.portals = [];
|
||||
response.portals.forEach(function(item, index) {
|
||||
$scope.portals.unshift({
|
||||
title: item.title,
|
||||
storage: item.storage,
|
||||
active: item.active,
|
||||
type: item.portalType,
|
||||
fullPath: item.location
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The status for the Evil Portal module as well as current portals should be retrieved when the controller loads.
|
||||
getStatus();
|
||||
getPortals();
|
||||
|
||||
|
||||
}]);
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="row" ng-controller="EvilPortalController">
|
||||
|
||||
<div class="col-md-3">
|
||||
<!-- Controls Panel -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<a href="javascript:;" data-toggle="collapse" data-target="#collapseControls" class="text-muted"><h4
|
||||
@@ -24,6 +25,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messages Panel -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<a href="javascript:;" data-toggle="collapse" data-target="#collapseMessages" class="text-muted"><h4
|
||||
@@ -37,10 +40,11 @@
|
||||
<table style="width:100%" ng-hide="(messages.length == 0)">
|
||||
<tr ng-repeat="message in messages">
|
||||
<td>
|
||||
<hr/>
|
||||
<h5><b>{{ message.title }}</b> <a ng-click="dismissMessage($index)" href="javascript:;"
|
||||
class="pull-right">Dismiss</a></h5>
|
||||
<fieldset>
|
||||
<legend><h5><b>{{ message.title }}</b> <a ng-click="dismissMessage($index)" href="javascript:;"
|
||||
class="pull-right">Dismiss</a></h5></legend>
|
||||
<p class="text-muted"><i>{{ message.msg }}</i></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -49,49 +53,65 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--<div ng-show="dependencies">-->
|
||||
<div class="panel-group" id="accordion">
|
||||
<div class="col-md-9">
|
||||
<!-- Library panel -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h5 class="panel-title"><a href="javascript:;" data-toggle="collapse" data-parent="#accordion"
|
||||
data-target="#collapseLibrary" class="text-muted">Work Bench</a></h5>
|
||||
data-target="#collapseLibrary" class="text-muted">Work Bench <b style="text-transform: capitalize"><i>{{ workshop.portal.title }}</i></b></a></h5>
|
||||
</div>
|
||||
<div id="collapseLibrary" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<div class="input-group" ng-show="library">
|
||||
<input type="text" class="form-control" placeholder="PortalName" name="portalName"
|
||||
ng-model="newPortalName">
|
||||
<span class="input-group-btn">
|
||||
<button ng-disabled="newPortalName == ''" class="btn btn-default" type="button" ng-click="createNewPortal()">Create New Portal</button>
|
||||
</span>
|
||||
<div class="input-group" ng-show="evilPortal.library">
|
||||
|
||||
<span class="input-group-btn">
|
||||
<select class="form-control ng-pristine ng-valid ng-touched" style="width:80px" ng-model="newPortal.type">
|
||||
<option value="basic">Basic</option>
|
||||
<option value="targeted">Targeted</option>
|
||||
</select>
|
||||
</span>
|
||||
|
||||
<input type="text" class="form-control" placeholder="Portal Name" name="portalName" ng-model="newPortal.name">
|
||||
|
||||
<span class="input-group-btn">
|
||||
<button ng-disabled="newPortal.name == ''" class="btn btn-default" type="button" ng-click="createNewPortal('internal')">Create New Portal</button>
|
||||
<button ng-disabled="newPortal.name == ''" class="btn btn-default" type="button" ng-click="createNewPortal('sd')" ng-show="evilPortal.sdAvailable">Create On SD Card</button>
|
||||
</span>
|
||||
</div>
|
||||
<hr ng-show="library"/>
|
||||
<div ng-show="portals.length > 0 && library == true">
|
||||
<hr ng-show="evilPortal.library"/>
|
||||
<div ng-show="portals.length > 0 && evilPortal.library">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" align="center">
|
||||
<thead>
|
||||
<th>Portal Name</th>
|
||||
<th>Portal Type</th>
|
||||
<th>Location</th>
|
||||
<th ng-show="evilPortal.sdAvailable">Move</th>
|
||||
<th>Activate</th>
|
||||
<th>Delete</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="portal in portals">
|
||||
<td><a href="javascript:;" ng-click="getPortalFiles(portal)"><b>{{ portal.title
|
||||
}}</b></a></td>
|
||||
<td class="text-muted"><i>{{ portal.storage }}</i></td>
|
||||
<td ng-hide="portal.active"><a href="javascript:;"
|
||||
ng-click="activatePortal(portal)">Activate</a>
|
||||
<!-- Open portal work shop -->
|
||||
<td><a href="javascript:;" ng-click="loadPortal(portal)" style="text-transform: capitalize"><b>{{ portal.title }}</b></a></td>
|
||||
<!-- Portal type -->
|
||||
<td class="text-muted" style="text-transform: capitalize"><i>{{ portal.type }}</i></td>
|
||||
<!-- What storage device the portal is on -->
|
||||
<td class="text-muted" style="text-transform: capitalize"><i>{{ portal.storage }}</i></td>
|
||||
<!-- change storage -->
|
||||
<td ng-show="evilPortal.sdAvailable">
|
||||
<a ng-show="(portal.storage == 'internal') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'sd');">Move to SD</a>
|
||||
<a ng-show="(portal.storage == 'sd') && !portal.active" href="javascript:;" ng-click="movePortal(portal, 'internal');">Move to Internal</a>
|
||||
</td>
|
||||
<td ng-show="portal.active"><a href="javascript:;"
|
||||
ng-click="deactivatePortal(portal)">Deactivate</a>
|
||||
<!-- activate/deactivate -->
|
||||
<td>
|
||||
<a ng-hide="portal.active" href="javascript:;" ng-click="activatePortal(portal)">Activate</a>
|
||||
<a ng-show="portal.active" href="javascript:;" ng-click="deactivatePortal(portal)">Deactivate</a>
|
||||
</td>
|
||||
<td ng-hide="portal.active"><a href="javascript:;" data-toggle="modal"
|
||||
data-target="#deleteModal"
|
||||
ng-click="deletePortalRequest(portal)"
|
||||
ng-hide="portal.storage == 'active'">Delete</a>
|
||||
<!-- delete portal button -->
|
||||
<td>
|
||||
<a ng-hide="portal.active" data-toggle="modal" data-target="#deleteModal" ng-click="deletePortal(false, portal)">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -99,28 +119,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-hide="portals.length > 0 || library == false">
|
||||
<div ng-hide="portals.length > 0 || !evilPortal.library">
|
||||
<p class="text-muted text-center"><i>No Portals in Library to Display.</i></p>
|
||||
</div>
|
||||
|
||||
<div ng-show="library == false">
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="library = true">Back To
|
||||
Library
|
||||
</button>
|
||||
<hr />
|
||||
<h3>Contents of {{ workshopPortal.name }}</h3>
|
||||
<!-- Portal Editor -->
|
||||
<div ng-hide="evilPortal.library">
|
||||
|
||||
<!-- Editor Buttons -->
|
||||
<div class="row-fluid">
|
||||
<span class="pull-left">
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="evilPortal.library = true; resetWorkshop()">Back To Library</button>
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#fileModal" ng-click="setupNewFile()">New File</button>
|
||||
<!-- Holding off on toggle commands until a future release. Still need to think through how they should work. -->
|
||||
<!--<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#commandEditorModal" ng-click="loadToggleCommands()">Toggle Commands</button>-->
|
||||
<button type="submit" class="btn btn-default btn-sm" data-toggle="modal" data-target="#ruleEditorModal" ng-show="workshop.portal.type == 'targeted'" ng-click="loadTargetedRules();">Target Rule Editor</button>
|
||||
<button type="submit" class="btn btn-default btn-sm" ng-click="loadPortal(workshop.portal)">Refresh</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<hr />
|
||||
|
||||
<!-- Portal Files -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" align="center">
|
||||
<thead>
|
||||
<th>File Name</th>
|
||||
<th>Edit</th>
|
||||
<th>Delete</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="file in workshopPortal.files">
|
||||
<td>{{ file }}</td>
|
||||
<td><a href="javascript:;" ng-click="editPortal(workshopPortal, file)"
|
||||
data-toggle="modal" data-target="#fileModal">Edit</a></td>
|
||||
<tr ng-repeat="file in workshop.dirContents">
|
||||
<td>{{ file.name }}</td>
|
||||
<td><a href="javascript:;" ng-click="loadFileContent(file.path)" data-toggle="modal" data-target="#fileModal">Edit</a></td>
|
||||
<td><a href="javascript:;" ng-click="workshop.deleteFile = file" data-toggle="modal" data-target="#deleteFileModal" ng-show="file.deletable">Delete</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -144,18 +179,18 @@
|
||||
<i>This is a list of clients who are allowed to connect to the internet without ever viewing the captive portal.</i>
|
||||
</p>
|
||||
<p>
|
||||
<textarea class="form-control" rows="15" ng-model="whiteList" readonly></textarea>
|
||||
<textarea id="whiteListPool" class="form-control" rows="15" ng-mouseup="getClickedClient('whiteListPool', 'whiteListInput');" ng-model="whiteList.clients" readonly></textarea>
|
||||
</p>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="IP Address" name="ipaddress"
|
||||
ng-model="whiteListInput">
|
||||
<input type="text" class="form-control" placeholder="IP Address" name="whiteListInput"
|
||||
ng-model="whiteList.toManipulate">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button"
|
||||
ng-click="addWhiteListClient()">Add</button>
|
||||
<button class="btn btn-default" type="button"
|
||||
ng-click="removeWhiteListClient()">Remove</button>
|
||||
<button class="btn btn-default" type="button" ng-click="addWhiteListClient()">Add</button>
|
||||
<button class="btn btn-default" type="button" ng-click="removeClientFromList('whiteList')">Remove</button>
|
||||
</span>
|
||||
</div>
|
||||
<br/>
|
||||
<button class="btn btn-default btn-sm" type="button" ng-click="getList('whiteList')">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,22 +204,24 @@
|
||||
</div>
|
||||
<div id="collapseAuthorizedClients" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<p class="text-muted" ng-show="running">
|
||||
<i>This is a list of clients who have been authorized through the captive portal.</i>
|
||||
</p>
|
||||
<p ng-show="running">
|
||||
<textarea class="form-control" rows="15" ng-model="accessList" readonly></textarea>
|
||||
</p>
|
||||
<div class="input-group" ng-show="running">
|
||||
<input type="text" class="form-control" placeholder="IP Address" name="ipaddress"
|
||||
ng-model="accessListInput">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button"
|
||||
ng-click="authorizeClient()">Authorize</button>
|
||||
<button class="btn btn-default" type="button" ng-click="revokeClient()">Revoke</button>
|
||||
</span>
|
||||
<div ng-show="evilPortal.running">
|
||||
<p class="text-muted">
|
||||
<i>This is a list of clients who have been authorized through the captive portal.</i>
|
||||
</p>
|
||||
<p>
|
||||
<textarea id='authorizedPool' class="form-control" rows="15" ng-mouseup="getClickedClient('authorizedPool', 'accessListInput');" ng-model="accessList.clients" readonly></textarea>
|
||||
</p>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="IP Address" name="accessListInput" ng-model="accessList.toManipulate">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="authorizeClient()">Authorize</button>
|
||||
<button class="btn btn-default" type="button" ng-click="removeClientFromList('accessList')">Revoke</button>
|
||||
</span>
|
||||
</div>
|
||||
<br />
|
||||
<button class="btn btn-default btn-sm" type="button" ng-click="getList('accessList')">Refresh</button>
|
||||
</div>
|
||||
<div ng-hide="running">
|
||||
<div ng-hide="evilPortal.running">
|
||||
<p class="text-muted text-center"><i>Evil Portal must be started first.</i></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -200,14 +237,12 @@
|
||||
</div>
|
||||
<div id="collapsePreview" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<div ng-show="running">
|
||||
<div ng-show="evilPortal.running">
|
||||
<iframe src="http://172.16.42.1" style="width:100%;height:300px;border:none;"
|
||||
id="livePreviewIframe"></iframe>
|
||||
<button type="submit" ng-click="refreshLivePreview()" class="btn btn-default btn-sm">
|
||||
Refresh
|
||||
</button>
|
||||
<button type="submit" ng-click="refreshLivePreview()" class="btn btn-default btn-sm">Refresh</button>
|
||||
</div>
|
||||
<div ng-hide="running">
|
||||
<div ng-hide="evilPortal.running">
|
||||
<p class="text-muted text-center"><i>Evil Portal must be started first.</i></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,35 +253,72 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h5 class="panel-title"><a href="javascript:;" data-toggle="collapse" data-parent="#accordion"
|
||||
data-target="#collapseChangelog" class="text-muted">Evil Portal Change
|
||||
Log</a></h5>
|
||||
data-target="#collapseChangelog" class="text-muted">Evil Portal Info</a></h5>
|
||||
</div>
|
||||
<div id="collapseChangelog" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<ul>
|
||||
<li><b>2.1</b></li>
|
||||
<fieldset>
|
||||
<legend>Disclaimer</legend>
|
||||
<p><b>Evil Portal and all of its components are intended for professional use only. No one associated with this project is an anyway liable for your actions.</b></p>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Help</legend>
|
||||
<h5>Summary</h5>
|
||||
<p>Evil Portal is a captive portal program that enables you to easily create a captive portal for whatever your needs are. There are two kinds of portals Basic and Targeted.
|
||||
Basic portals are just a simple page that gets served to all clients. Targeted portals allow you to serve a unique page to different clients based on a given rule.</p>
|
||||
<h5>Basic Portals</h5>
|
||||
<p>A Basic Portal is a one-size serves all kind of portal. These portals are designed to be a single page that all clients will land on. This is the traditional way
|
||||
that captive portals work and the way Evil Portal as been doing things from the beginning.</p>
|
||||
<h5>Targeted Portals</h5>
|
||||
<p>A Targeted Portal allows you to serve a different page on a per-client basis based on a condition. This can be something like their mac address, user-agent, etc...
|
||||
Targeted Portals give you a whole new dynamic to what Evil Portal can do, if you want clients who are connected to the SSID "Coffee Shop" to see a "Coffee Shop" branded portal
|
||||
while at the same time clients with Android phones seeing an Android branded portal then this is what you want.</p>
|
||||
<h5>Useful Links</h5>
|
||||
<a href="https://forums.hak5.org/index.php?/topic/37874-official-evilportal/">Hak5 Forum Thread</a>
|
||||
<br />
|
||||
<a href="https://github.com/frozenjava/EvilPortalNano">GitHub Project</a>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Change Log</legend>
|
||||
<ul>
|
||||
<li class="text-muted">Removed un-needed verbosity</li>
|
||||
<li class="text-muted">Made tab key indent in the editor instead of change elements</li>
|
||||
<li class="text-muted">Added confirmation dialogue box when deleting a portal</li>
|
||||
<li class="text-muted">Created auto-start feature</li>
|
||||
<li class="text-muted">Various other quality of life updates</li>
|
||||
<li><b>3.0</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Added ability to route clients to different portals based upon some identifier [ssid, mac vendor, ip, etc...]</li>
|
||||
<li class="text-muted">Updated the work bench so users can choose between targeted and non-targeted portals</li>
|
||||
<li class="text-muted">Created easy-to-use interface for creating targeting rules</li>
|
||||
<li class="text-muted">Created some consistency throughout the UI</li>
|
||||
<li class="text-muted">Added ability to create portals on an SD card and move between SD and Internal storage easily</li>
|
||||
<li class="text-muted">Made white listed and authorized clients IP addresses clickable like SSIDs in PineAP</li>
|
||||
<li class="text-muted">Created helper functions getClientMac, getClientSSID, getClientHostName</li>
|
||||
<li class="text-muted">Sending notification when a client goes through a portal.</li>
|
||||
<li class="text-muted">Various quality of life improvements</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><b>2.0</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Captive Portal is now purely iptables (because F***
|
||||
NoDogSplash)
|
||||
</li>
|
||||
<li><b>2.1</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Removed un-needed verbosity</li>
|
||||
<li class="text-muted">Made tab key indent in the editor instead of change elements</li>
|
||||
<li class="text-muted">Added confirmation dialogue box when deleting a portal</li>
|
||||
<li class="text-muted">Created auto-start feature</li>
|
||||
<li class="text-muted">Various other quality of life updates</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><b>1.0</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Initial Pineapple Nano Release</li>
|
||||
<li><b>2.0</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Captive Portal is now purely iptables (because F***
|
||||
NoDogSplash)
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><b>1.0</b></li>
|
||||
<ul>
|
||||
<li class="text-muted">Initial Pineapple Nano Release</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,6 +326,7 @@
|
||||
</div>
|
||||
<!--</div>-->
|
||||
|
||||
<!-- Edit file modal -->
|
||||
<div id="fileModal" class="modal fade" role="dialog">
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -263,7 +336,7 @@
|
||||
|
||||
if (keyCode == 9) {
|
||||
e.preventDefault();
|
||||
var start = $(this).get(0).selectionStart;
|
||||
var start = $(this).get(0).selection;
|
||||
var end = $(this).get(0).selectionEnd;
|
||||
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
@@ -284,30 +357,27 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
ng-click="editPortalFile = {}">×</button>
|
||||
<h4 class="modal-title">File Editor {{ editPortalFile.file }}</h4>
|
||||
ng-click="workshop.editFile = {}">×</button>
|
||||
<h4 class="modal-title" ng-show="!workshop.editFile.isNewFile">Editing File {{ workshop.editFile.name }}</h4>
|
||||
<h4 class="modal-title" ng-show="workshop.editFile.isNewFile">Creating New File</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label">File Name</label>
|
||||
<input type="text" class="form-control" ng-model="editPortalFile.file"
|
||||
placeholder="Notes.txt" disabled>
|
||||
<input type="text" class="form-control" ng-model="workshop.editFile.name" placeholder="Notes.txt" ng-disabled="!workshop.editFile.isNewFile">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">File Contents</label> <a href="javascript:;"
|
||||
ng-click="editPortalFile.code = ''">Clear</a>
|
||||
<textarea class="form-control" rows="10" ng-model="editPortalFile.code"
|
||||
<label class="control-label">File Contents</label> <a href="javascript:;" ng-click="workshop.editFile.content = null">Clear</a>
|
||||
<textarea class="form-control" rows="10" ng-model="workshop.editFile.content"
|
||||
placeholder="Write some text here" id="evilportalEditor"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" ng-click="savePortalCode(editPortalFile)" class="btn btn-success pull-left"
|
||||
data-dismiss="modal">Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-default pull-right" data-dismiss="modal"
|
||||
ng-click="editPortalFile = {}">Cancel
|
||||
<button type="button" ng-click="saveFileContent(workshop.editFile);" class="btn btn-success pull-right" data-dismiss="modal" ng-disabled="workshop.editFile.content == null">Save</button>
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal"
|
||||
ng-click="workshop.editFile = {}">Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -315,7 +385,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Delete file Modal -->
|
||||
<!-- Delete Portal Modal -->
|
||||
<div id="deleteModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
@@ -323,7 +393,7 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="portalToDelete = null; portalDeleteValidation = null">×</button>
|
||||
<h4 class="modal-title">Delete Portal {{ portalToDelete.title }}</h4>
|
||||
<h4 class="modal-title">Delete Portal <i>{{ portalToDelete.title }}</i>?</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete {{ portalToDelete.title }} on {{ portalToDelete.storage }} storage. Once you do this it can not be undone.</p>
|
||||
@@ -331,8 +401,121 @@
|
||||
<input type="text" class="form-control" placeholder="PortalName" name="portalName" ng-model="portalDeleteValidation">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary pull-left" data-dismiss="modal" ng-click="portalToDelete = null; portalDeleteValidation = null">Cancel</button>
|
||||
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="deletePortal(portalToDelete)" ng-disabled="portalDeleteValidation != portalToDelete.title">Delete</button>
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="portalToDelete = null; portalDeleteValidation = null">Cancel</button>
|
||||
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="deletePortal(true, portalToDelete)" ng-disabled="portalDeleteValidation.toLowerCase() != portalToDelete.title.toLowerCase()">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete file Modal -->
|
||||
<div id="deleteFileModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="workshop.deleteFile = {}">×</button>
|
||||
<h4 class="modal-title">Delete File <i><b>{{ workshop.deleteFile.name }}</b></i>?</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You are about to delete <b>{{ workshop.deleteFile.path }}</b>. Once you do this it can not be undone.</p>
|
||||
<p><b>Are you absolutely sure you want to delete <i>{{ workshop.deleteFile.name }}</i>?</b></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="workshop.deleteFile = {}">Cancel</button>
|
||||
<button type="button" class="btn btn-danger pull-right" data-dismiss="modal" ng-click="deleteFile()">Delete {{ workshop.deleteFile.name }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rule Editor Modal -->
|
||||
<div id="ruleEditorModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" ng-click="">×</button>
|
||||
<h4 class="modal-title">Editing Rules for {{ workshop.portal.title }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div ng-repeat="(key, data) in workshop.concreteTargetedRules.rules">
|
||||
<fieldset>
|
||||
<legend style="text-transform: uppercase">{{ key }}</legend>
|
||||
<div ng-repeat="(specifier, values) in data">
|
||||
<h5 style="text-transform: capitalize">{{ specifier }} <a href="javascript:;" data-target="#" ng-click="newTargetedRule(key, specifier);">Add Rule</a></h5>
|
||||
<div class="table-responsive" ng-hide="isObjectEmpty(workshop.workingTargetedRules.rules[key][specifier])">
|
||||
<table class="table table-striped" align="center">
|
||||
<thead>
|
||||
<th>Rule Key</th>
|
||||
<th>Destination</th>
|
||||
<!--<th>Commit</th>-->
|
||||
<th>Remove</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="(i, rules) in workshop.workingTargetedRules.rules[key][specifier]">
|
||||
<td><input type="text" placeholder="Key Value" ng-model="rules.key"></td>
|
||||
<td><input type="text" placeholder="Destination.php" ng-model="rules.destination"></td>
|
||||
<!--<td><a href="javascript:;" data-target="#" ng-click="commitPortalRule(key, specifier, i, rules.key, rules.destination)">Commit</a></td>-->
|
||||
<td><button ng-click="removeTargetedRule(key, specifier, i)" class="btn btn-sm btn-danger">Remove</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal" ng-click="">Cancel</button>
|
||||
<button type="button" class="btn btn-success pull-right" data-dismiss="modal" ng-click="saveTargetedRules()" >Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toggled command editor -->
|
||||
<div id="commandEditorModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Toggle Commands for {{ workshop.portal.title }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
<legend>On Activation - <a ng-click="saveToggleCommands('enable')">Save</a></legend>
|
||||
<div class="center-block">
|
||||
<!-- For sake of getting EP 3.0 release, I'm not going to implement this just yet.
|
||||
<span class="center-block">
|
||||
<button type="submit" class="btn btn-link btn-sm">Enable PineAP</button>
|
||||
<button type="submit" class="btn btn-link btn-sm">Enable Karma Associations</button>
|
||||
<button type="submit" class="btn btn-link btn-sm">Enable Beacon Response</button>
|
||||
</span>-->
|
||||
</div>
|
||||
<textarea style="width:100%;" rows="10" ng-model="workshop.onEnable"></textarea>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>On Deactivation - <a ng-click="saveToggleCommands('disable')">Save</a></legend>
|
||||
<div class="row-fluid">
|
||||
<!-- For sake of getting EP 3.0 release, I'm not going to implement this just yet.
|
||||
<span class="center-block">
|
||||
<button type="submit" class="btn btn-link btn-sm">Disable PineAP</button>
|
||||
<button type="submit" class="btn btn-link btn-sm">Disable Karma Associations</button>
|
||||
<button type="submit" class="btn btn-link btn-sm">Disable Beacon Response</button>
|
||||
</span>-->
|
||||
</div>
|
||||
<textarea style="width:100%;" rows="10" ng-model="workshop.onDisable"></textarea>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-success pull-right" data-dismiss="modal" ng-click="saveToggleCommands('all')">Save All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"author": "newbi3",
|
||||
"description": "An Evil Captive Portal.",
|
||||
"devices": [
|
||||
"nano",
|
||||
"tetra"
|
||||
],
|
||||
"title": "Evil Portal",
|
||||
"version": "2.1"
|
||||
"title": "Evil Portal",
|
||||
"version": "3.0",
|
||||
"author": "newbi3",
|
||||
"description": "An Evil Captive Portal."
|
||||
}
|
||||
Reference in New Issue
Block a user