Subversion Repositories web_pages

Rev

Rev 18 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
18 rodolico 1
<?php
2
 
3
/**
4
 * Copyright (c) 2025, Daily Data, Inc.
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without modification,
8
 * are permitted provided that the following conditions are met:
9
 *
10
 * 1. Redistributions of source code must retain the above copyright notice, this list
11
 *    of conditions and the following disclaimer.
12
 * 2. Redistributions in binary form must reproduce the above copyright notice, this
13
 *    list of conditions and the following disclaimer in the documentation and/or other
14
 *    materials provided with the distribution.
15
 * 3. Neither the name of Daily Data, Inc. nor the names of its contributors may be
16
 *    used to endorse or promote products derived from this software without specific
17
 *    prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
22
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
24
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
28
 * DAMAGE.
29
 */
30
 
31
/**
32
 * PHP script which serves files (QR code images or OpenVPN configuration files)
33
 */
34
 
35
 
36
// /var/www/html/web_pages/totp_opnsense/auto.php
37
declare(strict_types=1);
38
 
19 rodolico 39
const API_KEY = 'moustache.SCULPTURE.glancing';
18 rodolico 40
const FLAG_PATH = '/tmp/totp_opnsense.flag';
41
 
42
define('VERSION', '0.1.0');
43
 
44
// The router must be specified
45
$router = '';
46
# flag file for signaling other scripts
47
$refreshFile = '/tmp/update_';
48
 
49
// this is the only variable you may need to change
50
// everything else is in the configuration file
51
$configFile = __DIR__ . '/users.json';
52
 
53
// we need the config data for form processing and validation, so always load it
54
if (file_exists($configFile)) {
55
    $configData = json_decode(file_get_contents($configFile), true);
56
} else {
57
    die("Configuration file $configFile not found");
58
}
59
 
60
 
61
function http_text($code, $msg) {
62
   http_response_code($code);
63
   header('Content-Type: text/plain; charset=utf-8');
64
   echo $msg;
65
   exit;
66
}
67
 
68
// Accept parameters from GET or POST
69
$user = isset($_REQUEST['user']) ? (string) $_REQUEST['user'] : '';
70
$key = isset($_REQUEST['key']) ? (string) $_REQUEST['key'] : '';
71
$filetype = isset($_REQUEST['filetype']) ? (string) $_REQUEST['filetype'] : '';
72
$router = isset($_REQUEST['router']) ? (string) $_REQUEST['router'] : '';
73
 
74
// Get download token for this router, error if it does not exist
75
$downloadToken = $configData[$router]['downloadToken'] ?? '';
76
if ( $downloadToken === '') {
77
   // no download token configured for this router
78
   http_text(403, 'Unconfigured');
79
}
80
 
81
 
82
// Basic validation
83
if ( file_exists(FLAG_PATH) || $user === '' || $key === '' || $filetype === '' || $router === '') {
19 rodolico 84
   http_text(503, 'Missing parameters or service unavailable');
18 rodolico 85
}
86
 
87
// validate download token
88
if ( !hash_equals($downloadToken, $key)) {
89
   // create flag file for hacking attempt and exit
90
   $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
91
   if (strpos($ip, ',') !== false) {
92
      $ip = trim(explode(',', $ip)[0]);
93
   }
94
   @file_put_contents(FLAG_PATH, 'hacking_attempt ' . $ip . ' ' . date('c') . PHP_EOL, LOCK_EX);
95
   http_text(503, 'Service unavailable');
96
}
97
 
98
// Sanitize router to prevent traversal and bad characters
99
$sanitized = preg_replace('/[^A-Za-z0-9_-]/', '', $router);
100
if ($sanitized === '' || $sanitized !== $router || !array_key_exists($sanitized, $configData )) {
101
   http_text(400, 'Invalid router');
102
} else {
103
   $router = $sanitized;
104
}
105
 
106
// Sanitize username to prevent traversal and bad characters
107
$sanitized = preg_replace('/[^A-Za-z0-9_-]/', '', $user);
108
if ($sanitized === '' || $sanitized !== $user || !array_key_exists($sanitized, $configData[$router]['users'] )) {
109
   http_text(400, 'Invalid username');
110
} else {
111
   $user = $sanitized;
112
}
113
 
114
// Determine file to serve or action
115
$baseDir = __DIR__;
116
$filename = '';
117
$contentType = 'application/octet-stream';
118
 
119
 
120
switch (strtolower($filetype)) {
121
   case 'ovpn':
122
      $filename = $configData[$router]['users'][$user]['ovpnFile'];
123
      $contentType = 'application/x-openvpn-profile';
124
      break;
125
   case 'qr':
126
      $filename = $configData[$router]['users'][$user]['qrFile'];
19 rodolico 127
      // .qrcode is a QR code image; send as binary download
18 rodolico 128
      $contentType = 'application/octet-stream';
129
      break;
130
   case 'refresh':
131
      // create flag file for processing by another script and return success
132
      @file_put_contents($refreshFile . $router, 'refresh_requested ' . $sanitized . ' ' . date('c') . PHP_EOL, LOCK_EX);
133
      http_text(200, 'Refresh queued, please wait 5 minutes before downloading new files.');
134
      break;
135
   default:
136
      http_text(400, 'Invalid filetype');
137
}
138
 
139
 
140
// Serve file if exists
141
$fullPath = $baseDir . DIRECTORY_SEPARATOR . $filename;
142
if (!is_file($fullPath) || !is_readable($fullPath)) {
143
   http_text(404, 'File not found');
144
}
145
 
146
// Send headers and file as download
147
header('Content-Description: File Transfer');
148
header('Content-Type: ' . $contentType);
149
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
150
header('Content-Length: ' . (string) filesize($fullPath));
151
header('Cache-Control: no-cache, must-revalidate');
152
readfile($fullPath);
153
exit;