Subversion Repositories web_pages

Rev

Rev 14 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 14 Rev 16
Line 1... Line 1...
1
#! /usr/bin/env perl
1
#! /usr/bin/env perl
-
 
2
 
-
 
3
# Copyright (c) 2025, Daily Data, Inc.
-
 
4
# All rights reserved.
-
 
5
#
-
 
6
# Redistribution and use in source and binary forms, with or without modification,
-
 
7
# are permitted provided that the following conditions are met:
2
 
8
#
-
 
9
# 1. Redistributions of source code must retain the above copyright notice, this list
-
 
10
#    of conditions and the following disclaimer.
-
 
11
# 2. Redistributions in binary form must reproduce the above copyright notice, this
-
 
12
#    list of conditions and the following disclaimer in the documentation and/or other
-
 
13
#    materials provided with the distribution.
-
 
14
# 3. Neither the name of Daily Data, Inc. nor the names of its contributors may be
-
 
15
#    used to endorse or promote products derived from this software without specific
-
 
16
#    prior written permission.
-
 
17
#
-
 
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-
 
19
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-
 
20
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
-
 
21
# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-
 
22
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
-
 
23
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-
 
24
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-
 
25
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-
 
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
-
 
27
# DAMAGE.
-
 
28
#
-
 
29
# Script which will read information from an OPNsense router via its API
-
 
30
# and create/update user OTP secrets, QR codes, and OpenVPN configuration files
-
 
31
# for each user with a VPN certificate.
3
 
32
#
-
 
33
# Change History:
-
 
34
#  v1.0 2025-10-01 - Initial version RWR
-
 
35
#     Initial Release
-
 
36
#  v1.0.1 2025-10-01 RWR
-
 
37
#     Added a lock file to not allow the script to run if it is already running. If we can not
-
 
38
#       get an exclusive lock on $lockFile in $lockRetryTime*$lockRetries seconds, script will abort
-
 
39
#     Added both the full path to the data directories as well as the relative, to help
-
 
40
#       the web site (relative) and the script (full path, even if we're running from a different)
-
 
41
#       location to work easily (less code)
-
 
42
 
4
use strict;
43
use strict;
5
use warnings;
44
use warnings;
6
 
45
 
-
 
46
# use libraries from the directory this script is in
-
 
47
BEGIN {
-
 
48
   use FindBin;
-
 
49
   use File::Spec;
7
use lib '.'; # our libraries are in the current directory
50
   # use libraries from the directory this script is in
-
 
51
   use Cwd 'abs_path';
-
 
52
   use File::Basename;
-
 
53
   use lib dirname( abs_path( __FILE__ ) );
-
 
54
}
-
 
55
 
8
 
56
 
9
use JSON; # for encode_json, decode_json
57
use JSON; # for encode_json, decode_json
10
use Data::Dumper; # for debugging
58
use Data::Dumper; # for debugging
11
use opnsense; # our module to handle opnsense API calls
59
use opnsense; # our module to handle opnsense API calls
12
use GD::Barcode::QRcode; # for creating the QR code images
60
use GD::Barcode::QRcode; # for creating the QR code images
13
use MIME::Base64 qw( decode_base64 ); # for decoding the ovpn file returned from the API
61
use MIME::Base64 qw( decode_base64 ); # for decoding the ovpn file returned from the API
14
use File::Path qw( make_path ); # for creating directories
62
use File::Path qw( make_path ); # for creating directories
15
   
-
 
16
# Configuration file path
-
 
17
my $config_file = 'config.json';
-
 
18
# Users file path. This is shared between loadOpnSense.pl and index.php
-
 
19
my $users_file = 'users.json';
-
 
20
# Constants for creation of QR image
-
 
21
# this is used in URL created
-
 
22
my $issuer = "OPNsense";
-
 
23
# The size of individual "pixels" in graphics
63
use Fcntl qw(:flock); # Import locking constants
24
my $moduleSize = 10;
-
 
25
# location to place the QR images
-
 
26
my $qrLocation = './qrcodes'; # cwd/qrcodes
-
 
27
# location to place the ovpn files
-
 
28
my $ovpnFileLocation = './openvpn_configs'; # cwd/openvpn
-
 
29
# following strings will be added to ovpn files if they don't exist
-
 
30
my $additionalOvpnStrings = "static-challenge 'Enter Auth Code' 0\nauth-nocache\n";
-
 
31
 
64
 
32
# load the configuration for the specified router from the config file
-
 
33
# returns a hash ref of the configuration for the specified entry (router name)
-
 
34
# if file does not exist or entry not found, returns empty hash ref
-
 
35
# file - name of file to read (default config.json if not specified)
-
 
36
# entry - name of the router to load configuration for
-
 
37
sub loadConfig {
65
our $VERSION = '1.0.1';
-
 
66
 
38
    my ($file, $entry) = @_;
67
# Check if running as root
39
    if ( -e $file ) {
68
if ($> != 0) {
40
        open my $fh, '<', $file or die "Could not open '$file' $!";
-
 
41
        local $/;
-
 
42
        my $json_text = <$fh>;
-
 
43
        close $fh;
-
 
44
         my $config = decode_json($json_text);
69
   die "Error: This script must be run as root.\n";
45
         return $config->{$entry} if (exists $config->{$entry});
-
 
46
    }
-
 
47
    return {};
-
 
48
}
70
}
49
 
71
 
-
 
72
#-----------------------------
-
 
73
# Configuration Variables
-
 
74
#-----------------------------
-
 
75
# Location of the configuration files and directories where we will store data
-
 
76
# relative to the script directory as written
-
 
77
my $scriptDir = $FindBin::RealBin;
-
 
78
my $configFile = $scriptDir . '/routers.json';
-
 
79
my $usersFile = $scriptDir . '/users.json';
-
 
80
my $qrLocation =  './qrcodes';
-
 
81
my $ovpnFileLocation =  './openvpn_configs';
-
 
82
# size of the QR code modules, basically a magnification factor
-
 
83
my $moduleSize = 10;
50
# load the users data structure from the specified file
84
# strings to add to the ovpn file if not already present
-
 
85
my $additionalOvpnStrings = "static-challenge 'Enter Auth Code' 0\nauth-nocache\n";
-
 
86
# using a lock file to ensure script does not run while another request has it running
-
 
87
my $lockFile = '/tmp/opnsense.lock';
-
 
88
my $lockRetryTime = 10; # number of seconds between subsequent tries for a lock
-
 
89
my $lockRetries = 6; # number of times we will attempt to get a lock before failing
-
 
90
 
-
 
91
#-----------------------------
-
 
92
# slurpFile: Reads in the entire contents of a file and returns it as a string
-
 
93
# $file - name of file to read
-
 
94
# returns the contents of the file as a string, or empty string if file does not exist
-
 
95
# since we do this a lot, make it a separate function. This is likely the most efficient way
-
 
96
# to read a file in Perl
-
 
97
#-----------------------------
51
sub loadUsers {
98
sub slurpFile {
52
   my ( $file ) = @_;
99
   my ($file) = @_;
53
   my $data = {};
100
   my $data = '';
54
   if ( -e $file ) {
101
   if (-e $file) {
55
      open( my $fh, '<', $file ) or die "Cannot open $file: $!";
102
      open(my $fh, '<', $file) or die "Cannot open $file: $!";
56
      local $/;  # slurp mode
103
      local $/;  # slurp mode
57
      my $json_text = <$fh>;
104
      $data = <$fh>;
58
      close $fh;
105
      close $fh;
59
      $data = decode_json($json_text);
-
 
60
   }
106
   }
61
   return $data;
107
   return $data;
62
}
108
}
63
 
109
 
-
 
110
#-----------------------------
-
 
111
# loadConfig: Load router configuration from config file
-
 
112
# $file - name of file to read
-
 
113
# $entry - name of the router entry to load
-
 
114
# returns a reference to the data structure for the router, or empty hash if not found
-
 
115
#-----------------------------
-
 
116
sub loadConfig {
-
 
117
   my ($file, $entry) = @_;
-
 
118
   my $return = &slurpFile($file);
-
 
119
   return {} unless $return && $return ne '';
-
 
120
   my $data = decode_json($return);
-
 
121
   return $data->{$entry} if (exists $data->{$entry});
-
 
122
   return {};
-
 
123
}
-
 
124
 
-
 
125
#-----------------------------
-
 
126
# loadUsers: Load users data structure from file
-
 
127
# $file - name of file to read
-
 
128
# returns a reference to the data structure
-
 
129
# $file assumed to be in JSON format
-
 
130
#-----------------------------
-
 
131
sub loadUsers {
-
 
132
   my ($file) = @_;
-
 
133
   my $data = &slurpFile($file);
-
 
134
   return {} unless $data && $data ne '';
-
 
135
   my $return = decode_json($data);
-
 
136
   return {} unless $return && ref($return) eq 'HASH';
-
 
137
   return $return;
-
 
138
}
-
 
139
 
-
 
140
#-----------------------------
64
# save the users data structure to the specified file
141
# saveUsers: Save users data structure to file
-
 
142
# $file - name of file to write
-
 
143
# $data - reference to the data to write
-
 
144
# $timeStampFile - optional name of a file to write the current timestamp to
-
 
145
# writes the data in JSON format
-
 
146
#-----------------------------
65
sub saveUsers {
147
sub saveUsers {
66
   my ( $file, $data ) = @_;
148
   my ($file, $data) = @_;
67
   open( my $fh, '>', $file ) or die "Cannot open $file: $!";
149
   open(my $fh, '>', $file) or die "Cannot open $file: $!";
68
   print $fh encode_json($data);
150
   print $fh encode_json($data);
69
   close $fh;
151
   close $fh;
70
}
152
}
71
 
153
 
72
#
-
 
-
 
154
#-----------------------------
73
# makeQR
155
# makeQR: Creates a QR code file
74
#
-
 
75
# Creates a QR file with a name of ($routername_)$account.png
156
# $routerName - name of the router (used in filename and OTP URL)
76
# where $routername is prepended if it is set and $account is the user name
157
# $account - account name (used in filename and OTP URL)
-
 
158
# $secret - OTP secret
-
 
159
# $moduleSize - size of the QR code modules
77
# an underscore separates the routername and the account (if routername is set)
160
# $relativeDir - directory to save the QR code file
78
# the contents of the qr are made up of the constants 'otpauth://totp/' followed by $issuer
161
# $absDir - The directory the script is in, so writing relative directories will correctly place
79
# and the parameters secret (otp code) and issuer again.
162
# returns the filename of the created QR code image
-
 
163
#-----------------------------
80
sub makeQR {
164
sub makeQR {
-
 
165
   # Creates a QR code image for the user's OTP secret
81
   my ( $routerName, $account, $secret, $issuer, $moduleSize, $qrLocation ) = @_;
166
   my ($routerName, $account, $secret, $moduleSize, $relativeDir, $absDir ) = @_;
82
   my $otp_url = "otpauth://totp/$issuer:$account?secret=$secret&issuer=$issuer";
167
   # build the OTP URL to include router name as issuer and the account name for display in authenticator apps
83
   # Ecc (Error Correction Code) can be one of Low, Medium, Quartile, and High
168
   my $otpUrl = "otpauth://totp/$routerName:$account?secret=$secret&issuer=$routerName";
84
   # ModuleSize increases the actual size of each "pixel", enlarging for easier reading
169
   # generate the QR code
85
   my $barcode = GD::Barcode::QRcode->new($otp_url, { Ecc => 'M', ModuleSize => $moduleSize } );
170
   my $barcode = GD::Barcode::QRcode->new($otpUrl, { Ecc => 'M', ModuleSize => $moduleSize } );
86
   my $image = $barcode->plot();
171
   my $image = $barcode->plot();
87
   # Save the image to a file. If there is a router name, prepend it to the filename with an underscore
172
   my $fileName = ($routerName ? $routerName . '_' : '' ) . $account . '.png'; 
88
   # allows the same user to be on two different routers
173
   # This allows us to retain the relative file path for storage, but ensuring we write the file to 
89
   my $outputFile = $qrLocation . '/' . ($routerName ? $routerName . '_' : '' ) . $account . '.png';
174
   # the correct path even if we are not running the script from the script directory.
90
   open my $out, '>', "$outputFile" or die "Cannot write file $outputFile: $!";
175
   open my $out, '>', "$absDir/" . $fileName or die "Cannot write file $fileName to $absDir: $!";
91
   binmode $out;
176
   binmode $out;
92
   print $out $image->png;
177
   print $out $image->png;
93
   close $out;
178
   close $out;
94
   return $outputFile;
179
   return $relativeDir . '/' . $fileName;
95
}
180
}
96
 
181
 
97
#
-
 
98
# makeVPNConfigFile
182
#-----------------------------
99
#
-
 
100
# Creates a configuration file for the user based on the template provided
183
# makeVPNConfigFile: Creates a VPN configuration file for the user
-
 
184
# $contents - reference to the hash containing the 'content' key with base64 encoded ovpn file
101
# The template is read in and the following substitutions are made:
185
# $routerName - name of the router (used in filename)
-
 
186
# $userName - name of the user (used in filename)
-
 
187
# $ovpnLocation - The relative path to the output directory
-
 
188
# $absDir - The absolute path to the output directory
-
 
189
# $additionalOvpnStrings - additional strings to add to the ovpn file if not already present
-
 
190
# returns the filename of the created ovpn file
-
 
191
#-----------------------------
102
sub makeVPNConfigFile {
192
sub makeVPNConfigFile {
103
   my ( $contents, $routername, $username, $ovpnLocation, $additionalOvpnStrings ) = @_;
193
   my ($contents, $routerName, $userName, $ovpnLocation, $absDir, $additionalOvpnStrings) = @_;
104
   # if contents is empty or does not have a content key, return empty string
-
 
105
   return '' unless $contents && $contents->{'content'};
194
   return '' unless $contents && $contents->{'content'};
106
   my $outputFileName = $ovpnLocation . '/' . ($routername ? $routername . '_' : '' ) . $username . '.ovpn';
195
   my $fileName = ($routerName ? $routerName . '_' : '' ) . $userName . '.ovpn';
107
   $contents->{'content'} = decode_base64($contents->{'content'});
196
   $contents->{'content'} = decode_base64($contents->{'content'});
108
   $contents->{'content'} .= "\n";
197
   $contents->{'content'} .= "\n";
109
   $contents->{'content'} =~ s/(\r?\n)+/\n/g; # normalize line endings
198
   $contents->{'content'} =~ s/(\r?\n)+/\n/g; # normalize line endings
110
   # add any additional strings if they are not already present
-
 
111
   my $comment = '';
199
   my $comment = '';
112
   if ( $contents->{'content'} !~ /# Added by loadOpnSense.pl/ ) {
200
   if ( $contents->{'content'} !~ /# Added by loadOpnSense.pl/ ) {
113
      $comment = "# Added by loadOpnSense.pl\n";
201
      $comment = "# Added by loadOpnSense.pl\n";
114
   }
202
   }
115
   foreach my $string ( split( /\n/, $additionalOvpnStrings ) ) {
203
   foreach my $string ( split( /\n/, $additionalOvpnStrings ) ) {
116
      unless ( $contents->{'content'} =~ /\$string/ ) {
204
      unless ( $contents->{'content'} =~ /\$string/ ) {
117
         $contents->{'content'} .= "$comment$string\n";
205
         $contents->{'content'} .= "$comment$string\n";
118
         $comment = ''; # only add comment once
206
         $comment = ''; # only add comment once
119
      }
207
      }
120
   }
208
   }
-
 
209
   # This allows us to retain the relative file path for storage, but ensuring we write the file to 
-
 
210
   # the correct path even if we are not running the script from the script directory.
121
   open my $out, '>', "$outputFileName" or die "Cannot write file $outputFileName: $!";
211
   open my $out, '>', "$absDir/" . $fileName or die "Cannot write file $fileName to $absDir: $!";
122
   print $out $contents->{'content'};
212
   print $out $contents->{'content'};
123
   close $out;
213
   close $out;
124
   return $outputFileName;
214
   return $ovpnLocation . '/' . $fileName;
-
 
215
}
-
 
216
 
-
 
217
# cleanOldDataFiles: Deletes old data files in the specified directory matching the given pattern
-
 
218
# $directory - directory to scan for old files
-
 
219
# $pattern - regex pattern to match files to delete
-
 
220
# simply removes all files matching the pattern
-
 
221
#-----------------------------
-
 
222
sub cleanOldDataFiles {
-
 
223
   my ( $directory, $pattern ) = @_;
-
 
224
   opendir(my $dh, $directory) or die "Cannot open directory $directory: $!";
-
 
225
   while (my $file = readdir($dh)) {
-
 
226
      next if ($file =~ /^\./);
-
 
227
      if ($file =~ /$pattern/) {
-
 
228
         unlink "$directory/$file" or warn "Could not delete $directory/$file: $!";
-
 
229
      }
-
 
230
   }
-
 
231
   closedir($dh);
125
}
232
}
126
 
233
 
127
 
234
 
-
 
235
#-----------------------------
128
### Main program
236
# Main program
-
 
237
#-----------------------------
-
 
238
 
-
 
239
# make sure script does not run more than once at the same time
-
 
240
# Open the lock file
-
 
241
open(my $fh, '>', $lockFile) or die "Cannot open lock file $lockFile: $!";
-
 
242
# Attempt to acquire an exclusive lock. We will try $lockTimeOut times, each
-
 
243
# waiting $lockRetryTime
-
 
244
while ( $lockRetries-- ) {
-
 
245
   if (flock($fh, LOCK_EX | LOCK_NB)) {
-
 
246
      last;
-
 
247
   } else {
-
 
248
      sleep($lockRetryTime);
-
 
249
   }
-
 
250
}
-
 
251
# if $lockTimeOut is zero, we could not get a lock in, so die
-
 
252
die "Another instance of the script is already running and retries exceeded!\n" unless ( $lockRetries );
129
 
253
 
130
# get the router name from the command line
254
# get the router name from the command line
131
my $router = shift @ARGV or die "Usage: $0 <router_name>\n";
255
my $router = shift @ARGV or die "Usage: $0 <router_name>\n";
132
# Load existing configuration or initialize a new one
256
# Load existing configuration or initialize a new one
133
my $config = &loadConfig($config_file, $router );
257
my $config = &loadConfig($configFile, $router );
134
die "Configuration for router $router not found in $config_file\n" unless $config && ref($config) eq 'HASH';
258
die "Configuration for router $router not found in $configFile\n" unless $config && ref($config) eq 'HASH';
135
 
259
 
136
# load the users file. We will update the entry for this router
260
# load the users file. We will update the entry for this router
137
my $users = &loadUsers( $users_file );
261
my $users = &loadUsers( $usersFile );
138
 
262
 
139
# if there is no entry for this router, create an empty one
263
# if there is no entry for this router, create an empty one
140
$users->{$router} = {} unless exists $users->{$router};
264
$users->{$router} = {} unless exists $users->{$router};
141
$users->{$router}->{'qrLocation'} = $qrLocation unless exists $users->{$router}->{'qrLocation'};
265
$users->{$router}->{'qrLocation'} = $qrLocation unless exists $users->{$router}->{'qrLocation'};
142
$users->{$router}->{'ovpnLocation'} = $ovpnFileLocation unless exists $users->{$router}->{'ovpnLocation'};
266
$users->{$router}->{'ovpnLocation'} = $ovpnFileLocation unless exists $users->{$router}->{'ovpnLocation'};
143
$users->{$router}->{'users'} = {}; # clean out the users list, we will reload it
267
$users->{$router}->{'users'} = {}; # clean out the users list, we will reload it
-
 
268
# and get the location on the file system in case we are not running the script from the script directory
-
 
269
$users->{$router}->{'qrLocationFileystem'} = abs_path( $scriptDir . '/' . $qrLocation )
-
 
270
   unless exists $users->{$router}->{'qrLocationFileystem'};
-
 
271
$users->{$router}->{'ovpnLocationFileSystem'} = abs_path( $scriptDir . '/' . $ovpnFileLocation )
-
 
272
   unless exists $users->{$router}->{'ovpnLocationFileSystem'};
-
 
273
 
144
 
274
 
145
# ensure the directories exist and give them full access
275
# ensure the directories exist and give them full access
146
make_path( $users->{$router}->{'qrLocation'}, 0777 ) unless -d $users->{$router}->{'qrLocation'};
276
make_path( $users->{$router}->{'qrLocationFileystem'}, 0777 ) unless -d $users->{$router}->{'qrLocationFileystem'};
147
make_path( $users->{$router}->{'ovpnLocation'}, 0777 ) unless -d $users->{$router}->{'ovpnLocation'};
277
make_path( $users->{$router}->{'ovpnLocationFileSystem'}, 0777 ) unless -d $users->{$router}->{'ovpnLocationFileSystem'};
-
 
278
 
-
 
279
 
-
 
280
 
-
 
281
# instead of actually going through and cleaning files no longer in the users list,
-
 
282
# just delete all files for this router and recreate them
-
 
283
&cleanOldDataFiles( $users->{$router}->{'qrLocationFileystem'}, qr/^\Q$router\E_.*\.png$/ );
-
 
284
&cleanOldDataFiles( $users->{$router}->{'ovpnLocationFileSystem'}, qr/^\Q$router\E_.*\.ovpn$/ );
148
 
285
 
149
# this does most of the work, all the API calls are handled in the module
286
# this does most of the work, all the API calls are handled in the module
150
# create the opnsense object
287
# create the opnsense object
151
my $opnsense = new opnsense(
288
my $opnsense = opnsense->new(
152
   url    => $config->{'url'},
289
   url    => $config->{'url'},
153
   apiKey    => $config->{'apiKey'},
290
   apiKey    => $config->{'apiKey'},
154
   apiSecret => $config->{'apiSecret'},
291
   apiSecret => $config->{'apiSecret'},
155
   ovpnIndex  => $config->{'ovpnIndex'},
292
   ovpnIndex  => $config->{'ovpnIndex'},
156
   localport => $config->{'localport'},
293
   localPort => $config->{'localPort'},
157
   template  => $config->{'template'},
294
   template  => $config->{'template'},
158
   hostname  => $config->{'hostname'}, 
295
   hostname  => $config->{'hostname'}, 
159
);
296
);
160
 
297
 
161
# get the VPN users. This is a hashref keyed by cert name, value is username
298
# get the VPN users. This is a hashref keyed by cert name, value is username
162
my $vpnCerts = $opnsense->get_vpn_users();
299
my $vpnCerts = $opnsense->getVpnUsers();
163
 
300
 
164
# convert the cert-"user" to username->certs, array ref of certs as not sure if multiple certs per user allowed
301
# convert the cert-"user" to username->certs, array ref of certs as not sure if multiple certs per user allowed
165
foreach my $cert ( keys %$vpnCerts ) {
302
foreach my $cert ( keys %$vpnCerts ) {
166
   push @{$users->{$router}->{'users'}->{$vpnCerts->{$cert}}->{'certs'}}, $cert;
303
   push @{$users->{$router}->{'users'}->{$vpnCerts->{$cert}}->{'certs'}}, $cert;
167
}
304
}
168
 
305
 
169
# these are all of the users on the system
306
# these are all of the users on the system
170
my $allUsers = $opnsense->get_all_users();
307
my $allUsers = $opnsense->getAllUsers();
171
 
308
 
172
# for each user in the system, if they are in the vpnUsers list, copy otp_seed, cert, and password
309
# for each user in the system, if they are in the vpnUsers list, copy otp_seed, cert, and password
173
# we'll also delete any users who are disabled
310
# we'll also delete any users who are disabled
174
my @keys = ('otp_seed', 'cert', 'password' ); # these are the keys we want to copy
311
my @keys = ('otp_seed', 'cert', 'password' ); # these are the keys we want to copy
175
foreach my $user ( keys %$allUsers ) {
312
foreach my $user ( keys %$allUsers ) {
Line 184... Line 321...
184
}
321
}
185
 
322
 
186
# create the QR code file and VPN configuration for each user
323
# create the QR code file and VPN configuration for each user
187
foreach my $entry ( keys %{$users->{$router}->{'users'}} ) {
324
foreach my $entry ( keys %{$users->{$router}->{'users'}} ) {
188
   my $account = $entry;
325
   my $account = $entry;
189
   my $secret = $users->{$router}->{'users'}->{$entry}->{'otp_seed'};
326
   my $secret = $users->{$router}->{'users'}->{$entry}->{'otp_seed'} || '';
-
 
327
   # Create the QR code file and store the filename in the data structure
-
 
328
   # If there is no secret, do not create a QR code
190
   $users->{$router}->{'users'}->{$entry}->{'qrFile'} = 
329
   $users->{$router}->{'users'}->{$entry}->{'qrFile'} = 
-
 
330
      $secret && $secret ne '' ?
-
 
331
      &makeQR(
-
 
332
            $router,
-
 
333
            $account,
-
 
334
            $secret,
-
 
335
            $moduleSize,
191
      &makeQR($router, $account, $secret, $issuer, $moduleSize, $users->{$router}->{'qrLocation'});
336
            $users->{$router}->{'qrLocation'},
-
 
337
            $users->{$router}->{'qrLocationFileystem'}
-
 
338
            ) :
-
 
339
      '';
-
 
340
 
-
 
341
   # Create the VPN configuration file, if it exists. If multiple certs, only create the first one
-
 
342
   # warn user if there are multiple certs
192
   if ( scalar(@{$users->{$router}->{'users'}->{$entry}->{'certs'}}) > 1 ) {
343
   if ( scalar(@{$users->{$router}->{'users'}->{$entry}->{'certs'}}) > 1 ) {
193
      warn "$entry has multiple certs, using first one only\n";
344
      warn "$entry has multiple certs, using first one only\n";
194
   }
345
   }
195
   my $cert = $opnsense->get_vpn_config( $users->{$router}->{'users'}->{$entry}->{'certs'}->[0] );
346
   my $cert = $opnsense->getVpnConfig( $users->{$router}->{'users'}->{$entry}->{'certs'}->[0] );
196
   if ( !$cert || ref($cert) ne 'HASH' || !exists $cert->{'content'} ) {
347
   if ( !$cert || ref($cert) ne 'HASH' || !exists $cert->{'content'} ) {
197
      warn "Could not get VPN configuration for $entry, cert $users->{$router}->{'users'}->{$entry}->{'certs'}->[0]\n";
348
      warn "Could not get VPN configuration for $entry, cert $users->{$router}->{'users'}->{$entry}->{'certs'}->[0]\n";
198
      next;
349
      next;
199
   }
350
   }
200
   $users->{$router}->{'users'}->{$entry}->{'ovpnFile'} = 
351
   $users->{$router}->{'users'}->{$entry}->{'ovpnFile'} = 
-
 
352
      &makeVPNConfigFile( 
-
 
353
         $cert,
-
 
354
         $router,
-
 
355
         $entry,
-
 
356
         $users->{$router}->{'ovpnLocation'},
201
      &makeVPNConfigFile( $cert, $router, $entry, $users->{$router}->{'ovpnLocation'}, $additionalOvpnStrings );
357
         $users->{$router}->{'ovpnLocationFileSystem'},
-
 
358
         $additionalOvpnStrings );
202
}
359
}
203
 
-
 
-
 
360
# update the timestamp for the current router
-
 
361
$users->{$router}->{'lastUpdate'} = time;
204
# save the users file
362
# save the users file
205
&saveUsers( $users_file, $users );
363
&saveUsers( $usersFile, $users );
-
 
364
 
-
 
365
# Remove the lock file so the script can run again
-
 
366
close($fh);
-
 
367
unlink($lockFile) or warn "Could not unlink lock file: $!";
-
 
368
 
-
 
369
1;