Subversion Repositories web_pages

Rev

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

Rev Author Line No. Line
12 rodolico 1
<?php
16 rodolico 2
 
14 rodolico 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
 */
12 rodolico 30
 
31
/**
32
 
14 rodolico 33
   PHP script which reads configuration file containing user information. Users
34
   are presented with a username and password box and a router selector.
35
   When processed, will read data file and determine if the credentials match any line
36
   and
37
   * display a QR Code suitable for scanning with one time authentication app
38
   * display the OTP seed code in case user wants to enter it manually
39
   * provide a link to download the OpenVPN configuration file for that user
12 rodolico 40
 
14 rodolico 41
   Assumes configuration file generated by loadOpnSense.pl
42
   Assumes QR code images are pre-generated and stored in a web accessible location
43
   Assumes OpenVPN configuration files are pre-generated and stored in a web accessible location
44
   Assumes passwords are hashed using password_hash function
12 rodolico 45
 
16 rodolico 46
 
14 rodolico 47
   Version 1.0.0 RWR 2025-09-21
48
      Initial Release
49
   Version 1.1.0 RWR 2025-09-27
50
      Added capability of downloading VPN configuration file
51
   Version 1.2.0 RWR 2025-10-01
52
     Reading from json file instead of csv
53
     Removed dependency on exec and external commands
54
     Added logging of successful logins
55
     automatically generate router list from config file
56
     Added some error checking for missing qr code image file and ovpn file
20 rodolico 57
   Version 1.3.0 RWR 2026-01-04
58
     Changed log file date format to YYYY-MM-DD for better sorting
59
     Moved log files to logs/ subdirectory, auto-creates if needed
60
     Added support for multiple OVPN files per user with separate download links
12 rodolico 61
 
62
 */
63
 
20 rodolico 64
   define('VERSION', '1.3.0');
16 rodolico 65
 
14 rodolico 66
// this is the only variable you may need to change
67
// everything else is in the configuration file
16 rodolico 68
$configFile = __DIR__ . '/users.json';
13 rodolico 69
 
16 rodolico 70
$isValidUser = false;
71
 
14 rodolico 72
// we need the config data for form processing and validation, so always load it
16 rodolico 73
if (file_exists($configFile)) {
74
    $configData = json_decode(file_get_contents($configFile), true);
14 rodolico 75
} else {
16 rodolico 76
    die("Configuration file $configFile not found");
14 rodolico 77
}
13 rodolico 78
 
12 rodolico 79
// Check if the form is submitted
16 rodolico 80
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset( $_REQUEST['btnLogin'])) {
81
    // Get the username and password from the form
12 rodolico 82
    $username = $_POST['username'];
83
    $password = $_POST['password'];
84
    $router = $_POST['router'];
85
 
16 rodolico 86
    if ($configData) {
87
        $isValidUser = false;
88
        if (isset($configData[$router]) && isset($configData[$router]['users'][$username])) {
89
            $data = $configData[$router]['users'][$username];
90
            if (password_verify($password, $data['password'])) {
91
                $isValidUser = true;
92
                $code = $data['otp_seed'];
93
                $imageFileName = $data['qrFile'];
20 rodolico 94
                $ovpnFileName = is_array($data['ovpnFile']) ? $data['ovpnFile'] : [$data['ovpnFile']];
95
                if (!is_dir('./logs')) {
96
                    mkdir('./logs', 0755, true);
97
                }
16 rodolico 98
                $log = date("Y-m-d H:i:s") . "\t" . $_SERVER['REMOTE_ADDR'] . "\t" .
99
                "Success\t" . $username."\t" .PHP_EOL;
20 rodolico 100
                file_put_contents('./logs/log_'.date("Y-m-d").'.log', $log, FILE_APPEND);
16 rodolico 101
            }
102
        }
103
    } else {
104
        print '<h1>Could not open the configuration file.</h1>';
105
    }
106
} else if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['refreshRouter'])) {
107
    $router = preg_replace('/[^A-Za-z0-9_-]/', '', $_POST['refreshRouter']);
108
    $file = "/tmp/update_$router";
109
    file_put_contents($file, time());
110
    exit;
111
}
13 rodolico 112
 
12 rodolico 113
?>
114
 
115
<!DOCTYPE html>
116
<html lang="en">
117
<head>
16 rodolico 118
   <meta charset="UTF-8">
119
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
120
   <title>Get QR Code</title>
12 rodolico 121
</head>
122
<body>
16 rodolico 123
   <h1 style='text-align: center;'>OTP and OVPN configuration download</h1>
124
   <div id='debug'>
125
      <?php
126
         //print "<pre>REQUEST<br/>" . print_r( $_REQUEST, true) . "</pre>";
127
      ?>
128
 
129
   <div id='qr' style='text-align: center;'>
130
      <?php
131
         if ( isset( $_REQUEST['btnLogin']) ) {
132
            if ( $isValidUser ) {
133
               if (empty($code) || empty($imageFileName) || !file_exists($imageFileName)) {
134
                  print "<p><b>QR Code image file not found</b></p>";
135
                  print "<p>You may not be set up with a TOTP code on $router, talk to an administrator</p>";
136
               } else {
137
                  // all good
138
                  print "<img src='$imageFileName' alt='$code'>";
139
                  print "<br />Your code for $router is<br /><b>$code</b>";
140
               }
20 rodolico 141
               if (empty($ovpnFileName) || (is_array($ovpnFileName) && count($ovpnFileName) == 0)) {
16 rodolico 142
                  print "<br />No OpenVPN configuration file available";
143
               } else {
20 rodolico 144
                  print "<br />";
145
                  if (count($ovpnFileName) == 1) {
146
                     print "<a href='$ovpnFileName[0]' download>Download your OpenVPN Config File</a>";
147
                  } else {
148
                     print "Download OpenVPN Config Files:<br />";
149
                     foreach ($ovpnFileName as $index => $file) {
150
                        $fileName = basename($file);
151
                        print "<a href='$file' download>$fileName</a><br />";
152
                     }
153
                  }
16 rodolico 154
               }
15 rodolico 155
            } else {
16 rodolico 156
               print "<h1>Password wrong, or invalid user $username for router $router</h1>";
15 rodolico 157
            }
16 rodolico 158
         }
159
      ?>
160
   </div>
161
 
162
   <div id='login'>
163
      <form method="POST" action="">
164
         <label for="username">Username:</label>
165
         <input type="text" id="username" name="username" required>
166
         <br>
167
         <label for="password">Password:</label>
168
         <input type="password" id="password" name="password" required>
169
         <br>
170
         <label for="router">Router:</label>
171
            <select name="router" id="router" onchange="updateTimestampAndRefreshLink()">
172
               <?php
173
                  $routers = array_keys($configData);
174
                  foreach ($routers as $index => $name) {
175
                     print "<option value='$name'>$name</option>\n";
176
                  }
177
               ?>
178
            </select>        
179
         <br>
180
         <input type="submit" name="btnLogin" value="Login" id="loginBtn">
181
         <button type="button" id="refreshBtn" style="margin-left:10px;">Request Refresh</button>
182
         <div id="routerTimestamp" style="margin-top:10px;"></div>
183
         <div id="refreshDiv" style="margin-top:10px;"></div>
184
      </form>
185
   </div>
186
 
187
   <script>
188
   const configData = <?php echo json_encode($configData); ?>;
189
   function updateTimestampAndRefreshLink() {
190
      var router = document.getElementById('router').value;
191
      var tsDiv = document.getElementById('routerTimestamp');
192
      if (configData[router] && configData[router]['lastUpdate']) {
193
         var date = new Date(configData[router]['lastUpdate'] * 1000);
194
         tsDiv.innerHTML = '<b>Last update for ' + router + ':</b> ' + date.toLocaleString();
195
      } else {
196
         tsDiv.innerHTML = '<b>No update timestamp available for ' + router + '</b>';
14 rodolico 197
      }
16 rodolico 198
   }
199
   function requestRefresh(router) {
200
      var xhr = new XMLHttpRequest();
201
      xhr.open('POST', '', true);
202
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
203
      xhr.send('refreshRouter=' + encodeURIComponent(router));
204
      alert('Refresh requested for ' + router + "\nPlease wait 2 minutes before retrying");
205
   }
206
   document.getElementById('router').addEventListener('change', updateTimestampAndRefreshLink);
207
   window.onload = function() {
208
      updateTimestampAndRefreshLink();
209
      document.getElementById('refreshBtn').onclick = function() {
210
         var router = document.getElementById('router').value;
211
         requestRefresh(router);
212
      };
213
      document.getElementById('loginBtn').focus();
214
   };
215
   document.querySelector('form').addEventListener('keydown', function(e) {
216
      if (e.key === 'Enter') {
217
         e.preventDefault();
218
         document.getElementById('loginBtn').click();
219
      }
220
   });
221
   </script>
222
 
12 rodolico 223
</body>
224
</html>