Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
<?php
/**
* Copyright (c) 2025, Daily Data, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
* 3. Neither the name of Daily Data, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
/**
* PHP script which serves files (QR code images or OpenVPN configuration files)
*/
// /var/www/html/web_pages/totp_opnsense/auto.php
declare(strict_types=1);
const API_KEY = 'REPLACE_WITH_REAL_KEY';
const FLAG_PATH = '/tmp/totp_opnsense.flag';
define('VERSION', '0.1.0');
// The router must be specified
$router = '';
# flag file for signaling other scripts
$refreshFile = '/tmp/update_';
// this is the only variable you may need to change
// everything else is in the configuration file
$configFile = __DIR__ . '/users.json';
// we need the config data for form processing and validation, so always load it
if (file_exists($configFile)) {
$configData = json_decode(file_get_contents($configFile), true);
} else {
die("Configuration file $configFile not found");
}
function http_text($code, $msg) {
http_response_code($code);
header('Content-Type: text/plain; charset=utf-8');
echo $msg;
exit;
}
// Accept parameters from GET or POST
$user = isset($_REQUEST['user']) ? (string) $_REQUEST['user'] : '';
$key = isset($_REQUEST['key']) ? (string) $_REQUEST['key'] : '';
$filetype = isset($_REQUEST['filetype']) ? (string) $_REQUEST['filetype'] : '';
$router = isset($_REQUEST['router']) ? (string) $_REQUEST['router'] : '';
// Get download token for this router, error if it does not exist
$downloadToken = $configData[$router]['downloadToken'] ?? '';
if ( $downloadToken === '') {
// no download token configured for this router
http_text(403, 'Unconfigured');
}
// Basic validation
if ( file_exists(FLAG_PATH) || $user === '' || $key === '' || $filetype === '' || $router === '') {
http_text(503, 'Service unavailable');
}
// validate download token
if ( !hash_equals($downloadToken, $key)) {
// create flag file for hacking attempt and exit
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
if (strpos($ip, ',') !== false) {
$ip = trim(explode(',', $ip)[0]);
}
@file_put_contents(FLAG_PATH, 'hacking_attempt ' . $ip . ' ' . date('c') . PHP_EOL, LOCK_EX);
http_text(503, 'Service unavailable');
}
// Sanitize router to prevent traversal and bad characters
$sanitized = preg_replace('/[^A-Za-z0-9_-]/', '', $router);
if ($sanitized === '' || $sanitized !== $router || !array_key_exists($sanitized, $configData )) {
http_text(400, 'Invalid router');
} else {
$router = $sanitized;
}
// Sanitize username to prevent traversal and bad characters
$sanitized = preg_replace('/[^A-Za-z0-9_-]/', '', $user);
if ($sanitized === '' || $sanitized !== $user || !array_key_exists($sanitized, $configData[$router]['users'] )) {
http_text(400, 'Invalid username');
} else {
$user = $sanitized;
}
// Determine file to serve or action
$baseDir = __DIR__;
$filename = '';
$contentType = 'application/octet-stream';
switch (strtolower($filetype)) {
case 'ovpn':
$filename = $configData[$router]['users'][$user]['ovpnFile'];
$contentType = 'application/x-openvpn-profile';
break;
case 'qr':
$filename = $configData[$router]['users'][$user]['qrFile'];
// unknown content type for .qrcode; send as binary download
$contentType = 'application/octet-stream';
break;
case 'refresh':
// create flag file for processing by another script and return success
@file_put_contents($refreshFile . $router, 'refresh_requested ' . $sanitized . ' ' . date('c') . PHP_EOL, LOCK_EX);
http_text(200, 'Refresh queued, please wait 5 minutes before downloading new files.');
break;
default:
http_text(400, 'Invalid filetype');
}
// Serve file if exists
$fullPath = $baseDir . DIRECTORY_SEPARATOR . $filename;
if (!is_file($fullPath) || !is_readable($fullPath)) {
http_text(404, 'File not found');
}
// Send headers and file as download
header('Content-Description: File Transfer');
header('Content-Type: ' . $contentType);
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
header('Content-Length: ' . (string) filesize($fullPath));
header('Cache-Control: no-cache, must-revalidate');
readfile($fullPath);
exit;
Generated by GNU Enscript 1.6.5.90.