Subversion Repositories web_pages

Rev

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

Rev 18 Rev 20
Line 37... Line 37...
37
#     Added a lock file to not allow the script to run if it is already running. If we can not
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
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
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)
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)
41
#       location to work easily (less code)
-
 
42
#  v1.1.0 2026-01-04 RWR
-
 
43
#     Removed hardcoded additionalOvpnStrings variable
-
 
44
#     Added support for formats hash in router configuration
-
 
45
#     Modified makeVPNConfigFile to accept filename template with ROUTER and USER placeholders
-
 
46
#     Added support for multiple OVPN files per user based on formats configuration
-
 
47
#     Formats structure allows specifying filename and additionalStrings per format
-
 
48
#     Expands \n in additionalStrings to actual newlines in config files
42
 
49
 
43
use strict;
50
use strict;
44
use warnings;
51
use warnings;
45
 
52
 
46
# use libraries from the directory this script is in
53
# use libraries from the directory this script is in
Line 60... Line 67...
60
use GD::Barcode::QRcode; # for creating the QR code images
67
use GD::Barcode::QRcode; # for creating the QR code images
61
use MIME::Base64 qw( decode_base64 ); # for decoding the ovpn file returned from the API
68
use MIME::Base64 qw( decode_base64 ); # for decoding the ovpn file returned from the API
62
use File::Path qw( make_path ); # for creating directories
69
use File::Path qw( make_path ); # for creating directories
63
use Fcntl qw(:flock); # Import locking constants
70
use Fcntl qw(:flock); # Import locking constants
64
 
71
 
65
our $VERSION = '1.0.1';
72
our $VERSION = '1.1.0';
66
 
73
 
67
# Check if running as root
74
# Check if running as root
68
if ($> != 0) {
75
if ($> != 0) {
69
   die "Error: This script must be run as root.\n";
76
   die "Error: This script must be run as root.\n";
70
}
77
}
Line 79... Line 86...
79
my $usersFile = $scriptDir . '/users.json';
86
my $usersFile = $scriptDir . '/users.json';
80
my $qrLocation =  './qrcodes';
87
my $qrLocation =  './qrcodes';
81
my $ovpnFileLocation =  './openvpn_configs';
88
my $ovpnFileLocation =  './openvpn_configs';
82
# size of the QR code modules, basically a magnification factor
89
# size of the QR code modules, basically a magnification factor
83
my $moduleSize = 10;
90
my $moduleSize = 10;
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
91
# using a lock file to ensure script does not run while another request has it running
87
my $lockFile = '/tmp/opnsense.lock';
92
my $lockFile = '/tmp/opnsense.lock';
88
my $lockRetryTime = 10; # number of seconds between subsequent tries for a lock
93
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
94
my $lockRetries = 6; # number of times we will attempt to get a lock before failing
90
 
95
 
Line 180... Line 185...
180
}
185
}
181
 
186
 
182
#-----------------------------
187
#-----------------------------
183
# makeVPNConfigFile: Creates a VPN configuration file for the user
188
# makeVPNConfigFile: Creates a VPN configuration file for the user
184
# $contents - reference to the hash containing the 'content' key with base64 encoded ovpn file
189
# $contents - reference to the hash containing the 'content' key with base64 encoded ovpn file
185
# $routerName - name of the router (used in filename)
190
# $routerName - name of the router (used in filename template replacement)
186
# $userName - name of the user (used in filename)
191
# $userName - name of the user (used in filename template replacement)
187
# $ovpnLocation - The relative path to the output directory
192
# $ovpnLocation - The relative path to the output directory
188
# $absDir - The absolute path to the output directory
193
# $absDir - The absolute path to the output directory
-
 
194
# $filenameTemplate - template for filename with ROUTER and USER placeholders
189
# $additionalOvpnStrings - additional strings to add to the ovpn file if not already present
195
# $additionalOvpnStrings - additional strings to add to the ovpn file, \n will be expanded to newlines
190
# returns the filename of the created ovpn file
196
# returns the filename of the created ovpn file
191
#-----------------------------
197
#-----------------------------
192
sub makeVPNConfigFile {
198
sub makeVPNConfigFile {
193
   my ($contents, $routerName, $userName, $ovpnLocation, $absDir, $additionalOvpnStrings) = @_;
199
   my ($contents, $routerName, $userName, $ovpnLocation, $absDir, $filenameTemplate, $additionalOvpnStrings) = @_;
194
   return '' unless $contents && $contents->{'content'};
200
   return '' unless $contents && $contents->{'content'};
-
 
201
   # Replace ROUTER and USER in the filename template
195
   my $fileName = ($routerName ? $routerName . '_' : '' ) . $userName . '.ovpn';
202
   my $fileName = $filenameTemplate;
-
 
203
   $fileName =~ s/ROUTER/$routerName/g;
-
 
204
   $fileName =~ s/USER/$userName/g;
196
   $contents->{'content'} = decode_base64($contents->{'content'});
205
   $contents->{'content'} = decode_base64($contents->{'content'});
197
   $contents->{'content'} .= "\n";
206
   $contents->{'content'} .= "\n";
198
   $contents->{'content'} =~ s/(\r?\n)+/\n/g; # normalize line endings
207
   $contents->{'content'} =~ s/(\r?\n)+/\n/g; # normalize line endings
199
   my $comment = '';
208
   my $comment = '';
200
   if ( $contents->{'content'} !~ /# Added by loadOpnSense.pl/ ) {
209
   if ( $contents->{'content'} !~ /# Added by loadOpnSense.pl/ ) {
201
      $comment = "# Added by loadOpnSense.pl\n";
210
      $comment = "# Added by loadOpnSense.pl\n";
202
   }
211
   }
-
 
212
   # Expand \n to actual newlines in additionalOvpnStrings
-
 
213
   $additionalOvpnStrings =~ s/\\n/\n/g if $additionalOvpnStrings;
203
   foreach my $string ( split( /\n/, $additionalOvpnStrings ) ) {
214
   foreach my $string ( split( /\n/, $additionalOvpnStrings || '' ) ) {
-
 
215
      next if $string eq '';
204
      unless ( $contents->{'content'} =~ /\$string/ ) {
216
      unless ( $contents->{'content'} =~ /\Q$string\E/ ) {
205
         $contents->{'content'} .= "$comment$string\n";
217
         $contents->{'content'} .= "$comment$string\n";
206
         $comment = ''; # only add comment once
218
         $comment = ''; # only add comment once
207
      }
219
      }
208
   }
220
   }
209
   # This allows us to retain the relative file path for storage, but ensuring we write the file to 
221
   # This allows us to retain the relative file path for storage, but ensuring we write the file to 
Line 254... Line 266...
254
# get the router name from the command line
266
# get the router name from the command line
255
my $router = shift @ARGV or die "Usage: $0 <router_name>\n";
267
my $router = shift @ARGV or die "Usage: $0 <router_name>\n";
256
# Load existing configuration or initialize a new one
268
# Load existing configuration or initialize a new one
257
my $config = &loadConfig($configFile, $router );
269
my $config = &loadConfig($configFile, $router );
258
die "Configuration for router $router not found in $configFile\n" unless $config && ref($config) eq 'HASH';
270
die "Configuration for router $router not found in $configFile\n" unless $config && ref($config) eq 'HASH';
-
 
271
# die Dumper( $config ) . "\n";
259
 
272
 
260
# load the users file. We will update the entry for this router
273
# load the users file. We will update the entry for this router
261
my $users = &loadUsers( $usersFile );
274
my $users = &loadUsers( $usersFile );
262
 
275
 
263
# if there is no entry for this router, create an empty one
276
# if there is no entry for this router, create an empty one
Line 339... Line 352...
339
            $users->{$router}->{'qrLocation'},
352
            $users->{$router}->{'qrLocation'},
340
            $users->{$router}->{'qrLocationFileystem'}
353
            $users->{$router}->{'qrLocationFileystem'}
341
            ) :
354
            ) :
342
      '';
355
      '';
343
 
356
 
344
   # Create the VPN configuration file, if it exists. If multiple certs, only create the first one
357
   # Create the VPN configuration file(s), if they exist. If multiple certs, only use the first one
345
   # warn user if there are multiple certs
358
   # warn user if there are multiple certs
346
   if ( scalar(@{$users->{$router}->{'users'}->{$entry}->{'certs'}}) > 1 ) {
359
   if ( scalar(@{$users->{$router}->{'users'}->{$entry}->{'certs'}}) > 1 ) {
347
      warn "$entry has multiple certs, using first one only\n";
360
      warn "$entry has multiple certs, using first one only\n";
348
   }
361
   }
349
   my $cert = $opnsense->getVpnConfig( $users->{$router}->{'users'}->{$entry}->{'certs'}->[0] );
362
   my $cert = $opnsense->getVpnConfig( $users->{$router}->{'users'}->{$entry}->{'certs'}->[0] );
350
   if ( !$cert || ref($cert) ne 'HASH' || !exists $cert->{'content'} ) {
363
   if ( !$cert || ref($cert) ne 'HASH' || !exists $cert->{'content'} ) {
351
      warn "Could not get VPN configuration for $entry, cert $users->{$router}->{'users'}->{$entry}->{'certs'}->[0]\n";
364
      warn "Could not get VPN configuration for $entry, cert $users->{$router}->{'users'}->{$entry}->{'certs'}->[0]\n";
352
      next;
365
      next;
353
   }
366
   }
-
 
367
   
-
 
368
   # Process formats to create multiple OVPN files if formats are defined
-
 
369
   my @ovpnFiles = ();
-
 
370
   my $formats = $config->{'formats'};
-
 
371
   # die Dumper($config) . "\n";
-
 
372
   # die Dumper($formats) . "\n";
-
 
373
   if ( $formats && ref($formats) eq 'HASH' && keys %$formats ) {
-
 
374
      # Iterate through each format
-
 
375
      foreach my $formatName ( keys %$formats ) {
-
 
376
         my $format = $formats->{$formatName};
-
 
377
         next unless ref($format) eq 'HASH';
-
 
378
         my $filename = $format->{'filename'} || '';
-
 
379
         my $additionalStrings = $format->{'additionalStrings'} || '';
-
 
380
         next if $filename eq '';
-
 
381
         my $ovpnFile = &makeVPNConfigFile(
-
 
382
            $cert,
-
 
383
            $router,
-
 
384
            $entry,
-
 
385
            $users->{$router}->{'ovpnLocation'},
354
   $users->{$router}->{'users'}->{$entry}->{'ovpnFile'} = 
386
            $users->{$router}->{'ovpnLocationFileSystem'},
-
 
387
            $filename,
-
 
388
            $additionalStrings
-
 
389
         );
-
 
390
         push @ovpnFiles, $ovpnFile if $ovpnFile;
-
 
391
      }
-
 
392
   }
-
 
393
   
-
 
394
   # If no formats defined or no files created, create a default file
-
 
395
   if ( scalar(@ovpnFiles) == 0 ) {
-
 
396
      my $defaultFilename = $router . '_' . $entry . '.ovpn';
355
      &makeVPNConfigFile( 
397
      my $ovpnFile = &makeVPNConfigFile(
356
         $cert,
398
         $cert,
357
         $router,
399
         $router,
358
         $entry,
400
         $entry,
359
         $users->{$router}->{'ovpnLocation'},
401
         $users->{$router}->{'ovpnLocation'},
360
         $users->{$router}->{'ovpnLocationFileSystem'},
402
         $users->{$router}->{'ovpnLocationFileSystem'},
361
         $additionalOvpnStrings );
403
         $defaultFilename,
-
 
404
         "static-challenge 'Enter Auth Code' 0\nauth-nocache"
-
 
405
      );
-
 
406
      push @ovpnFiles, $ovpnFile if $ovpnFile;
-
 
407
   }
-
 
408
   
-
 
409
   # Store as array if multiple files, or single string if only one
-
 
410
   $users->{$router}->{'users'}->{$entry}->{'ovpnFile'} = 
-
 
411
      scalar(@ovpnFiles) > 1 ? \@ovpnFiles : $ovpnFiles[0];
362
}
412
}
363
# update the timestamp for the current router
413
# update the timestamp for the current router
364
$users->{$router}->{'lastUpdate'} = time;
414
$users->{$router}->{'lastUpdate'} = time;
365
# save the users file
415
# save the users file
366
&saveUsers( $usersFile, $users );
416
&saveUsers( $usersFile, $users );