| Line 44... |
Line 44... |
| 44 |
# Added support for formats hash in router configuration
|
44 |
# Added support for formats hash in router configuration
|
| 45 |
# Modified makeVPNConfigFile to accept filename template with ROUTER and USER placeholders
|
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
|
46 |
# Added support for multiple OVPN files per user based on formats configuration
|
| 47 |
# Formats structure allows specifying filename and additionalStrings per format
|
47 |
# Formats structure allows specifying filename and additionalStrings per format
|
| 48 |
# Expands \n in additionalStrings to actual newlines in config files
|
48 |
# Expands \n in additionalStrings to actual newlines in config files
|
| - |
|
49 |
# v1.1.1 2026-06-15 RWR
|
| - |
|
50 |
# Decodes base64 OVPN content only once per user, not per format
|
| - |
|
51 |
# Fixed bug in makeVPNConfigFile where content was being overwritten
|
| - |
|
52 |
# Cleans out old QR code and OVPN files for the router before recreating them
|
| 49 |
|
53 |
|
| 50 |
use strict;
|
54 |
use strict;
|
| 51 |
use warnings;
|
55 |
use warnings;
|
| 52 |
|
56 |
|
| 53 |
# use libraries from the directory this script is in
|
57 |
# use libraries from the directory this script is in
|
| Line 67... |
Line 71... |
| 67 |
use GD::Barcode::QRcode; # for creating the QR code images
|
71 |
use GD::Barcode::QRcode; # for creating the QR code images
|
| 68 |
use MIME::Base64 qw( decode_base64 ); # for decoding the ovpn file returned from the API
|
72 |
use MIME::Base64 qw( decode_base64 ); # for decoding the ovpn file returned from the API
|
| 69 |
use File::Path qw( make_path ); # for creating directories
|
73 |
use File::Path qw( make_path ); # for creating directories
|
| 70 |
use Fcntl qw(:flock); # Import locking constants
|
74 |
use Fcntl qw(:flock); # Import locking constants
|
| 71 |
|
75 |
|
| 72 |
our $VERSION = '1.1.0';
|
76 |
our $VERSION = '1.1.1';
|
| 73 |
|
77 |
|
| 74 |
# Check if running as root
|
78 |
# Check if running as root
|
| 75 |
if ($> != 0) {
|
79 |
if ($> != 0) {
|
| 76 |
die "Error: This script must be run as root.\n";
|
80 |
die "Error: This script must be run as root.\n";
|
| 77 |
}
|
81 |
}
|
| Line 200... |
Line 204... |
| 200 |
return '' unless $contents && $contents->{'content'};
|
204 |
return '' unless $contents && $contents->{'content'};
|
| 201 |
# Replace ROUTER and USER in the filename template
|
205 |
# Replace ROUTER and USER in the filename template
|
| 202 |
my $fileName = $filenameTemplate;
|
206 |
my $fileName = $filenameTemplate;
|
| 203 |
$fileName =~ s/ROUTER/$routerName/g;
|
207 |
$fileName =~ s/ROUTER/$routerName/g;
|
| 204 |
$fileName =~ s/USER/$userName/g;
|
208 |
$fileName =~ s/USER/$userName/g;
|
| - |
|
209 |
# modified to use a separate variable for the file contents since it was overwritten previously
|
| 205 |
$contents->{'content'} = decode_base64($contents->{'content'});
|
210 |
my $fileContents = $contents->{'content'};
|
| 206 |
$contents->{'content'} .= "\n";
|
211 |
$fileContents .= "\n";
|
| 207 |
$contents->{'content'} =~ s/(\r?\n)+/\n/g; # normalize line endings
|
212 |
$fileContents =~ s/(\r?\n)+/\n/g; # normalize line endings
|
| 208 |
my $comment = '';
|
213 |
my $comment = '';
|
| 209 |
if ( $contents->{'content'} !~ /# Added by loadOpnSense.pl/ ) {
|
214 |
if ( $fileContents !~ /# Added by opensense-totp-ovpn-export/ ) {
|
| 210 |
$comment = "# Added by loadOpnSense.pl\n";
|
215 |
$comment = "# Added by opensense-totp-ovpn-export\n";
|
| 211 |
}
|
216 |
}
|
| 212 |
# Expand \n to actual newlines in additionalOvpnStrings
|
217 |
# Expand \n to actual newlines in additionalOvpnStrings
|
| 213 |
$additionalOvpnStrings =~ s/\\n/\n/g if $additionalOvpnStrings;
|
218 |
$additionalOvpnStrings =~ s/\\n/\n/g if $additionalOvpnStrings;
|
| 214 |
foreach my $string ( split( /\n/, $additionalOvpnStrings || '' ) ) {
|
219 |
foreach my $string ( split( /\n/, $additionalOvpnStrings || '' ) ) {
|
| 215 |
next if $string eq '';
|
220 |
next if $string eq '';
|
| 216 |
unless ( $contents->{'content'} =~ /\Q$string\E/ ) {
|
221 |
unless ( $fileContents =~ /\Q$string\E/ ) {
|
| 217 |
$contents->{'content'} .= "$comment$string\n";
|
222 |
$fileContents .= "$comment$string\n";
|
| 218 |
$comment = ''; # only add comment once
|
223 |
$comment = ''; # only add comment once
|
| 219 |
}
|
224 |
}
|
| 220 |
}
|
225 |
}
|
| 221 |
# This allows us to retain the relative file path for storage, but ensuring we write the file to
|
226 |
# This allows us to retain the relative file path for storage, but ensuring we write the file to
|
| 222 |
# the correct path even if we are not running the script from the script directory.
|
227 |
# the correct path even if we are not running the script from the script directory.
|
| 223 |
open my $out, '>', "$absDir/" . $fileName or die "Cannot write file $fileName to $absDir: $!";
|
228 |
open my $out, '>', "$absDir/" . $fileName or die "Cannot write file $fileName to $absDir: $!";
|
| 224 |
print $out $contents->{'content'};
|
229 |
print $out $fileContents;
|
| 225 |
close $out;
|
230 |
close $out;
|
| 226 |
return $ovpnLocation . '/' . $fileName;
|
231 |
return $ovpnLocation . '/' . $fileName;
|
| 227 |
}
|
232 |
}
|
| 228 |
|
233 |
|
| 229 |
# cleanOldDataFiles: Deletes old data files in the specified directory matching the given pattern
|
234 |
# cleanOldDataFiles: Deletes old data files in the specified directory matching the given pattern
|
| Line 370... |
Line 375... |
| 370 |
my $formats = $config->{'formats'};
|
375 |
my $formats = $config->{'formats'};
|
| 371 |
# die Dumper($config) . "\n";
|
376 |
# die Dumper($config) . "\n";
|
| 372 |
# die Dumper($formats) . "\n";
|
377 |
# die Dumper($formats) . "\n";
|
| 373 |
if ( $formats && ref($formats) eq 'HASH' && keys %$formats ) {
|
378 |
if ( $formats && ref($formats) eq 'HASH' && keys %$formats ) {
|
| 374 |
# Iterate through each format
|
379 |
# Iterate through each format
|
| - |
|
380 |
$cert->{'content'} = decode_base64( $cert->{'content'} ); # decode once for all formats
|
| 375 |
foreach my $formatName ( keys %$formats ) {
|
381 |
foreach my $formatName ( keys %$formats ) {
|
| 376 |
my $format = $formats->{$formatName};
|
382 |
my $format = $formats->{$formatName};
|
| 377 |
next unless ref($format) eq 'HASH';
|
383 |
next unless ref($format) eq 'HASH';
|
| 378 |
my $filename = $format->{'filename'} || '';
|
384 |
my $filename = $format->{'filename'} || '';
|
| 379 |
my $additionalStrings = $format->{'additionalStrings'} || '';
|
385 |
my $additionalStrings = $format->{'additionalStrings'} || '';
|