Created
August 2, 2025 10:03
-
-
Save softdream1981/0bb394749c0c4f0e2473b7735255736d to your computer and use it in GitHub Desktop.
captive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Captive Portal Setup Script for Raspberry Pi 3 | |
| # Using TL-WN722N v1.10 (wlan1) | |
| # Run with: sudo bash setup_captive_portal.sh | |
| set -e # Exit on error | |
| # Colors for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| NC='\033[0m' # No Color | |
| # Function to print colored output | |
| print_status() { | |
| echo -e "${GREEN}[+]${NC} $1" | |
| } | |
| print_error() { | |
| echo -e "${RED}[!]${NC} $1" | |
| } | |
| print_warning() { | |
| echo -e "${YELLOW}[*]${NC} $1" | |
| } | |
| # Check if running as root | |
| if [ "$EUID" -ne 0 ]; then | |
| print_error "Please run as root (use sudo)" | |
| exit 1 | |
| fi | |
| print_status "Starting Captive Portal Setup..." | |
| # Update system and install required packages | |
| print_status "Updating system and installing required packages..." | |
| apt update | |
| apt install -y hostapd dnsmasq nginx php-fpm iptables-persistent | |
| # Stop services during configuration | |
| print_status "Stopping services..." | |
| systemctl stop hostapd 2>/dev/null || true | |
| systemctl stop dnsmasq 2>/dev/null || true | |
| systemctl stop nginx 2>/dev/null || true | |
| # Configure network interface | |
| print_status "Configuring network interface..." | |
| if ! grep -q "interface wlan1" /etc/dhcpcd.conf; then | |
| cat >> /etc/dhcpcd.conf << 'EOF' | |
| interface wlan1 | |
| static ip_address=192.168.4.1/24 | |
| nohook wpa_supplicant | |
| EOF | |
| fi | |
| # Configure hostapd | |
| print_status "Configuring hostapd..." | |
| cat > /etc/hostapd/hostapd.conf << 'EOF' | |
| interface=wlan1 | |
| driver=nl80211 | |
| ssid=GuestWiFi | |
| hw_mode=g | |
| channel=7 | |
| wmm_enabled=0 | |
| macaddr_acl=0 | |
| auth_algs=1 | |
| ignore_broadcast_ssid=0 | |
| EOF | |
| # Update hostapd default config | |
| sed -i 's/#DAEMON_CONF=""/DAEMON_CONF="\/etc\/hostapd\/hostapd.conf"/' /etc/default/hostapd | |
| # Configure dnsmasq | |
| print_status "Configuring dnsmasq..." | |
| mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig 2>/dev/null || true | |
| cat > /etc/dnsmasq.conf << 'EOF' | |
| interface=wlan1 | |
| dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h | |
| domain=wlan | |
| address=/#/192.168.4.1 | |
| EOF | |
| # Create web portal directory | |
| print_status "Creating web portal..." | |
| mkdir -p /var/www/portal | |
| chown -R www-data:www-data /var/www/portal | |
| # Create index.php | |
| cat > /var/www/portal/index.php << 'EOF' | |
| <?php | |
| session_start(); | |
| // Check if user is already authenticated | |
| if (isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) { | |
| header("Location: /success.php"); | |
| exit(); | |
| } | |
| // Handle form submission | |
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | |
| $name = $_POST['name'] ?? ''; | |
| $email = $_POST['email'] ?? ''; | |
| $agree = $_POST['agree'] ?? ''; | |
| if (!empty($name) && !empty($email) && $agree === 'yes') { | |
| $_SESSION['authenticated'] = true; | |
| $_SESSION['mac'] = $_SERVER['REMOTE_ADDR']; | |
| // Log user info | |
| $log_entry = date('Y-m-d H:i:s') . " - Name: $name, Email: $email, IP: " . $_SERVER['REMOTE_ADDR'] . "\n"; | |
| file_put_contents('/var/log/portal_users.log', $log_entry, FILE_APPEND); | |
| // Add firewall rule to allow this user | |
| $mac = trim(shell_exec("arp -n " . $_SERVER['REMOTE_ADDR'] . " | grep -o '[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}'")); | |
| if (!empty($mac)) { | |
| shell_exec("sudo /usr/local/bin/portal_allow.sh " . escapeshellarg($mac)); | |
| } | |
| header("Location: /success.php"); | |
| exit(); | |
| } else { | |
| $error = "Please fill in all fields and agree to the terms."; | |
| } | |
| } | |
| ?> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Guest WiFi Portal</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| background-color: #f0f0f0; | |
| margin: 0; | |
| padding: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| background-color: white; | |
| padding: 40px; | |
| border-radius: 10px; | |
| box-shadow: 0 0 20px rgba(0,0,0,0.1); | |
| max-width: 400px; | |
| width: 100%; | |
| } | |
| h1 { | |
| color: #333; | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 5px; | |
| color: #666; | |
| font-weight: bold; | |
| } | |
| input[type="text"], | |
| input[type="email"] { | |
| width: 100%; | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| border-radius: 5px; | |
| box-sizing: border-box; | |
| } | |
| .checkbox-group { | |
| display: flex; | |
| align-items: center; | |
| margin: 20px 0; | |
| } | |
| input[type="checkbox"] { | |
| margin-right: 10px; | |
| } | |
| button { | |
| width: 100%; | |
| padding: 12px; | |
| background-color: #007bff; | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| font-size: 16px; | |
| cursor: pointer; | |
| transition: background-color 0.3s; | |
| } | |
| button:hover { | |
| background-color: #0056b3; | |
| } | |
| .error { | |
| color: #dc3545; | |
| text-align: center; | |
| margin-bottom: 20px; | |
| } | |
| .terms { | |
| font-size: 12px; | |
| color: #666; | |
| margin-top: 20px; | |
| padding: 10px; | |
| background-color: #f8f9fa; | |
| border-radius: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Welcome to Guest WiFi</h1> | |
| <?php if (isset($error)): ?> | |
| <div class="error"><?php echo htmlspecialchars($error); ?></div> | |
| <?php endif; ?> | |
| <form method="POST"> | |
| <div class="form-group"> | |
| <label for="name">Name:</label> | |
| <input type="text" id="name" name="name" required> | |
| </div> | |
| <div class="form-group"> | |
| <label for="email">Email:</label> | |
| <input type="email" id="email" name="email" required> | |
| </div> | |
| <div class="checkbox-group"> | |
| <input type="checkbox" id="agree" name="agree" value="yes" required> | |
| <label for="agree">I agree to the terms and conditions</label> | |
| </div> | |
| <button type="submit">Connect to Internet</button> | |
| <div class="terms"> | |
| <strong>Terms of Use:</strong> By connecting to this network, you agree to use it responsibly and legally. We reserve the right to monitor and restrict access as needed. | |
| </div> | |
| </form> | |
| </div> | |
| </body> | |
| </html> | |
| EOF | |
| # Create success.php | |
| cat > /var/www/portal/success.php << 'EOF' | |
| <?php | |
| session_start(); | |
| if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) { | |
| header("Location: /"); | |
| exit(); | |
| } | |
| ?> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Connected!</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| background-color: #f0f0f0; | |
| margin: 0; | |
| padding: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| background-color: white; | |
| padding: 40px; | |
| border-radius: 10px; | |
| box-shadow: 0 0 20px rgba(0,0,0,0.1); | |
| text-align: center; | |
| max-width: 400px; | |
| } | |
| h1 { | |
| color: #28a745; | |
| margin-bottom: 20px; | |
| } | |
| p { | |
| color: #666; | |
| line-height: 1.6; | |
| } | |
| .icon { | |
| font-size: 60px; | |
| color: #28a745; | |
| margin-bottom: 20px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="icon">✓</div> | |
| <h1>Successfully Connected!</h1> | |
| <p>You now have internet access. Enjoy browsing!</p> | |
| <p>This window will redirect in 5 seconds...</p> | |
| </div> | |
| <script> | |
| setTimeout(function() { | |
| window.location.href = 'http://google.com'; | |
| }, 5000); | |
| </script> | |
| </body> | |
| </html> | |
| EOF | |
| # Set proper permissions for web files | |
| chown -R www-data:www-data /var/www/portal | |
| # Configure nginx | |
| print_status "Configuring nginx..." | |
| cat > /etc/nginx/sites-available/portal << 'EOF' | |
| server { | |
| listen 80 default_server; | |
| listen [::]:80 default_server; | |
| root /var/www/portal; | |
| index index.php index.html; | |
| server_name _; | |
| # Catch all requests | |
| location / { | |
| try_files $uri $uri/ /index.php?$args; | |
| } | |
| location ~ \.php$ { | |
| include snippets/fastcgi-php.conf; | |
| fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; | |
| } | |
| # Redirect all 404s to portal | |
| error_page 404 /index.php; | |
| } | |
| EOF | |
| # Enable nginx site | |
| ln -sf /etc/nginx/sites-available/portal /etc/nginx/sites-enabled/ | |
| rm -f /etc/nginx/sites-enabled/default | |
| # Create firewall allow script | |
| print_status "Creating firewall scripts..." | |
| cat > /usr/local/bin/portal_allow.sh << 'EOF' | |
| #!/bin/bash | |
| MAC=$1 | |
| if [ -z "$MAC" ]; then | |
| echo "Usage: $0 <MAC_ADDRESS>" | |
| exit 1 | |
| fi | |
| # Add iptables rule to allow this MAC address | |
| iptables -t mangle -I PREROUTING -m mac --mac-source $MAC -j RETURN | |
| EOF | |
| chmod +x /usr/local/bin/portal_allow.sh | |
| # Create iptables setup script | |
| cat > /usr/local/bin/setup_portal_iptables.sh << 'EOF' | |
| #!/bin/bash | |
| # Clear existing rules | |
| iptables -t nat -F | |
| iptables -t mangle -F | |
| # Enable IP forwarding | |
| echo 1 > /proc/sys/net/ipv4/ip_forward | |
| # Allow established connections | |
| iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT | |
| # Mark packets from unauthorized users | |
| iptables -t mangle -N captive_portal 2>/dev/null || true | |
| iptables -t mangle -F captive_portal 2>/dev/null || true | |
| iptables -t mangle -A PREROUTING -i wlan1 -j captive_portal | |
| # Allow DHCP and DNS | |
| iptables -t mangle -A captive_portal -p udp --dport 53 -j RETURN | |
| iptables -t mangle -A captive_portal -p udp --dport 67:68 -j RETURN | |
| # Allow access to portal server | |
| iptables -t mangle -A captive_portal -d 192.168.4.1 -j RETURN | |
| # Mark unauthorized packets | |
| iptables -t mangle -A captive_portal -j MARK --set-mark 99 | |
| # Redirect HTTP traffic to portal | |
| iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination 192.168.4.1:80 | |
| iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 443 -j DNAT --to-destination 192.168.4.1:80 | |
| # Drop all other marked packets | |
| iptables -A FORWARD -m mark --mark 99 -j DROP | |
| # Allow forwarding from wlan1 to eth0 | |
| iptables -A FORWARD -i wlan1 -o eth0 -m state --state NEW -j ACCEPT | |
| # NAT for internet access | |
| iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE | |
| EOF | |
| chmod +x /usr/local/bin/setup_portal_iptables.sh | |
| # Create systemd service | |
| print_status "Creating systemd service..." | |
| cat > /etc/systemd/system/captive-portal.service << 'EOF' | |
| [Unit] | |
| Description=Captive Portal IPTables Rules | |
| After=network.target | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/usr/local/bin/setup_portal_iptables.sh | |
| RemainAfterExit=yes | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| # Configure sudoers for PHP | |
| print_status "Configuring sudoers..." | |
| echo "www-data ALL=(ALL) NOPASSWD: /usr/local/bin/portal_allow.sh" > /etc/sudoers.d/captive-portal | |
| chmod 440 /etc/sudoers.d/captive-portal | |
| # Create log file | |
| touch /var/log/portal_users.log | |
| chown www-data:www-data /var/log/portal_users.log | |
| # Enable IP forwarding permanently | |
| print_status "Enabling IP forwarding..." | |
| sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf | |
| sysctl -p | |
| # Enable services | |
| print_status "Enabling services..." | |
| systemctl daemon-reload | |
| systemctl unmask hostapd | |
| systemctl enable hostapd | |
| systemctl enable dnsmasq | |
| systemctl enable nginx | |
| systemctl enable captive-portal | |
| # Save iptables rules | |
| print_status "Saving iptables rules..." | |
| iptables-save > /etc/iptables/rules.v4 | |
| print_status "Setup complete!" | |
| print_warning "The system will reboot in 10 seconds..." | |
| print_warning "After reboot:" | |
| print_warning "1. Connect to 'GuestWiFi' network" | |
| print_warning "2. Open any website to see the portal" | |
| print_warning "3. Monitor users: tail -f /var/log/portal_users.log" | |
| # Optional: Create uninstall script | |
| cat > /usr/local/bin/uninstall_captive_portal.sh << 'EOF' | |
| #!/bin/bash | |
| # Uninstall script for captive portal | |
| echo "Removing captive portal..." | |
| # Stop services | |
| systemctl stop hostapd | |
| systemctl stop dnsmasq | |
| systemctl stop captive-portal | |
| systemctl disable hostapd | |
| systemctl disable dnsmasq | |
| systemctl disable captive-portal | |
| # Remove files | |
| rm -f /etc/hostapd/hostapd.conf | |
| rm -f /etc/nginx/sites-enabled/portal | |
| rm -f /etc/nginx/sites-available/portal | |
| rm -rf /var/www/portal | |
| rm -f /usr/local/bin/portal_allow.sh | |
| rm -f /usr/local/bin/setup_portal_iptables.sh | |
| rm -f /etc/systemd/system/captive-portal.service | |
| rm -f /etc/sudoers.d/captive-portal | |
| rm -f /var/log/portal_users.log | |
| # Restore original dnsmasq config | |
| mv /etc/dnsmasq.conf.orig /etc/dnsmasq.conf 2>/dev/null || true | |
| # Clear iptables rules | |
| iptables -t nat -F | |
| iptables -t mangle -F | |
| echo "Captive portal removed. Please reboot." | |
| EOF | |
| chmod +x /usr/local/bin/uninstall_captive_portal.sh | |
| sleep 10 | |
| reboot |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment