Subversion Repositories web_pages

Rev

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.