Subversion Repositories web_pages

Rev

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

Rev Author Line No. Line
14 rodolico 1
#! /usr/bin/env perl
16 rodolico 2
 
14 rodolico 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:
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.
16 rodolico 28
#
29
# This script defines the routers that can be managed by loadOpnSense.pl
30
# It allows adding, editing, and removing router configurations stored in a JSON file.
31
#
32
# Change History:
33
#  v1.0.0 2025-10-01 RWR
34
#     Initial version 
14 rodolico 35
 
36
use strict;
37
use warnings;
38
 
16 rodolico 39
# use libraries from the directory this script is in
40
BEGIN {
41
   use FindBin;
42
   use File::Spec;
43
   # use libraries from the directory this script is in
44
   use Cwd 'abs_path';
45
   use File::Basename;
46
   use lib dirname( abs_path( __FILE__ ) );
47
}
48
 
14 rodolico 49
use JSON qw( decode_json encode_json );
50
use Data::Dumper;
51
use opnsense;
52
 
16 rodolico 53
our $VERSION = "1.0.0";
14 rodolico 54
 
16 rodolico 55
# Default config file name if not specified on command line. Defaults to 'routers.json' in the script directory
56
my $configFileName = $FindBin::RealBin . '/routers.json';
57
# fields used in the router configuration
58
my @fields = ( 'url', 'apiKey', 'apiSecret', 'ovpnIndex', 'template', 'hostname', 'localPort' );
59
 
60
# Check if running as root
61
if ($> != 0) {
62
   die "Error: This script must be run as root.\n";
63
}
64
 
65
#-----------------------------
66
# usage: Print usage message
67
#-----------------------------
14 rodolico 68
sub usage {
16 rodolico 69
   print "Usage: $0 <opnsenseConfigFile>\n";
14 rodolico 70
   exit 1;
71
}
72
 
16 rodolico 73
#-----------------------------
74
# readConfig: Reads in the configuration file and returns a hash ref of the data
75
# If file does not exist, returns empty hash ref
76
# file - name of file to read (default $configFileName)
77
#-----------------------------
14 rodolico 78
sub readConfig {
16 rodolico 79
   my ($file) = @_;
14 rodolico 80
   my $data = {};
16 rodolico 81
   if (-e $file) {
82
      open(my $fh, '<', $file) or die "Cannot open $file: $!";
14 rodolico 83
      local $/;  # slurp mode
16 rodolico 84
      my $jsonText = <$fh>;
14 rodolico 85
      close $fh;
16 rodolico 86
      $data = decode_json($jsonText);
14 rodolico 87
   }
88
   return $data;
89
}
90
 
16 rodolico 91
#-----------------------------
92
# writeConfig: Writes the given data to the specified file in JSON format
93
# file - name of file to write (default $configFileName)
94
# data - reference to the data to write
95
# permissions on the file are set to 0600 and ownership to root
96
# since it contains sensitive information
97
#-----------------------------
14 rodolico 98
sub writeConfig {
99
   my ( $file, $data ) = @_;
100
   open( my $fh, '>', $file ) or die "Cannot open $file: $!";
101
   print $fh encode_json($data);
102
   close $fh;
16 rodolico 103
   chmod 0600, $file or warn "Could not set permissions on $file: $!";
104
   chown 0, 0, $file or warn "Could not set ownership to root on $file: $!";
14 rodolico 105
}
106
 
16 rodolico 107
#-----------------------------
108
# trim: Remove leading and trailing whitespace from a string
109
# s - the string to trim
110
#-----------------------------
14 rodolico 111
sub trim {
112
   my ($s) = @_;
113
   $s =~ s/^\s+|\s+$//g if defined $s;
114
   return $s;
115
}
116
 
16 rodolico 117
#-----------------------------
118
# menuSelect: Display a menu of options and prompt the user to select one
119
# prompt - the prompt to display to the user
120
# options - reference to an array of option strings
121
# additionalText - reference to an array of additional text strings to display next to each option
122
# returns the selected option string (1-based index)
123
#-----------------------------
14 rodolico 124
sub menuSelect {
125
   my ( $prompt, $options, $additionalText ) = @_;
126
   print "$prompt\n";
127
   for my $i ( 0 .. $#$options ) {
128
      print "  " . ($i+1) . ". " . $options->[$i] . ($additionalText->[$i] ? $additionalText->[$i] : "") . "\n";
129
   }
130
   print "Select option (1-" . ($#$options + 1) . "): ";
131
   my $sel = <STDIN>;
132
   chomp $sel;
133
   if ( $sel !~ /^\d+$/ || $sel < 1 || $sel > ($#$options + 1) ) {
16 rodolico 134
      warn "Invalid selection\n";
135
      return menuSelect( $prompt, $options, $additionalText );
14 rodolico 136
   }
137
   return $options->[$sel - 1];
138
}
139
 
16 rodolico 140
#-----------------------------
141
# selectOvpnIndex: Prompts the user to select an ovpnIndex from the list
142
# in the OPNsense router specified in the config hash
143
# config - reference to the router's configuration hash
144
# returns the selected ovpnIndex string or undef if none selected
145
#-----------------------------
146
sub selectOvpnIndex {
147
   my ( $config ) = @_;
148
   if ( defined $config->{'url'} && defined $config->{'apiKey'} && defined $config->{'apiSecret'} ) {
149
      my $opnsense = new opnsense(
150
         url    => $config->{'url'},
151
         apiKey    => $config->{'apiKey'},
152
         apiSecret => $config->{'apiSecret'},
153
      );
154
      my $providers = $opnsense->getVpnProviders();
155
      if ( ref($providers) eq 'HASH' && keys %$providers ) {
156
         my @providerIndex = sort keys %$providers;
157
         my @additionalText = map { 
158
               defined $config->{'ovpnIndex'} &&
159
               ($_ eq $config->{'ovpnIndex'} ? " (current) " : " - ") . $providers->{$_} 
160
               } @providerIndex;
161
         my $selectedProvider = &menuSelect( "Select VPN Provider", \@providerIndex, \@additionalText );
162
         return $selectedProvider;
163
      } else {
164
         warn "No VPN providers found or error retrieving providers. Please check your connection details.\n";
165
      }
166
   } else {
167
      warn "Please set url, apiKey, and apiSecret before selecting ovpnIndex.\n";
168
   }
169
   return undef;
170
}
171
 
172
#-----------------------------
173
# editRouter: Edit the configuration for a router
174
# config - reference to the router's configuration hash
175
# routerName - name of the router being edited
176
# keys - reference to an array of keys (fields) that can be edited
177
# works on one router at a time, allows setting fields for that router
178
# returns true if configuration was saved, false if not
179
#-----------------------------
14 rodolico 180
sub editRouter {
16 rodolico 181
   my ( $config, $routerName, $keys ) = @_;
14 rodolico 182
   my $option = '';
16 rodolico 183
   my $saved = 1;
184
   my $currentRouter = $config->{$routerName};
185
   $currentRouter->{'template'} = 'PlainOpenVPN' unless defined $currentRouter->{'template'};
14 rodolico 186
   my $vpnProviders = ();
187
   while ( $option ne 'Done' ) {
16 rodolico 188
      print "\nEditing router configuration for $routerName" . ($saved ? "" : " (Changed)") . ":\n";
14 rodolico 189
      $option = &menuSelect( 
190
         "Select field to edit", 
16 rodolico 191
         [ @$keys, 'Save Config', 'Done' ], 
192
         [ map { defined $currentRouter->{$_} ? " (current: $currentRouter->{$_})" : " (not set)" } @$keys, '' ] 
14 rodolico 193
         );
16 rodolico 194
      if ( $option eq 'ovpnIndex' ) {
195
         $currentRouter->{'ovpnIndex'} = &selectOvpnIndex( $currentRouter );
196
         $saved = 0; # mark as unsaved
197
      } elsif ( $option eq 'Save Config' ) {
198
         writeConfig( $configFileName, $config );
199
         $saved = 1; # mark as saved
200
         print "Configuration saved to $configFileName\n";
201
      } elsif ($option eq 'Done') {
202
         $option = &menuSelect( "Exiting configuration editor, but changes were not saved.", [ 'Exit', 'Save and Exit' ] );
203
         if ( $option eq 'Save and Exit' ) {
204
            writeConfig( $configFileName, $config );
205
            $saved = 1; # mark as saved
206
            print "Configuration saved to $configFileName\n";
14 rodolico 207
         }
16 rodolico 208
         last;
209
      } else {
210
         $saved = 0; # mark as unsaved
211
         print "Enter value for $option: ";
212
         my $value = <STDIN>;
213
         chomp $value;
214
         $value = trim($value);
215
         $currentRouter->{$option} = $value if $value ne '';
14 rodolico 216
      }
217
   }
16 rodolico 218
   return $saved;
14 rodolico 219
}
220
 
16 rodolico 221
$configFileName = shift if @ARGV;
222
# read the configuration file, if it exists, and place hash in $config
14 rodolico 223
my $config = &readConfig( $configFileName );
16 rodolico 224
# display menu of existing routers plus option to add new router
14 rodolico 225
my $routerName = &menuSelect( "Select router to configure", [ keys %$config, 'Add new router' ] );
16 rodolico 226
# adding a new router, so get the name, then create an empty hashref for it
14 rodolico 227
if ( $routerName eq 'Add new router' ) {
228
   print "Enter new router name: ";
229
   $routerName = <STDIN>;
230
   chomp $routerName;
231
   $routerName = trim($routerName);
232
   $config->{$routerName} = {};
233
}
16 rodolico 234
# edit the router configuration
235
&editRouter( $config, $routerName, \@fields );
14 rodolico 236
 
237
 
16 rodolico 238
1;