| 14 |
rodolico |
1 |
#! /usr/bin/env perl
|
|
|
2 |
# Copyright (c) 2025, Daily Data, Inc.
|
|
|
3 |
# All rights reserved.
|
|
|
4 |
#
|
|
|
5 |
# Redistribution and use in source and binary forms, with or without modification,
|
|
|
6 |
# are permitted provided that the following conditions are met:
|
|
|
7 |
#
|
|
|
8 |
# 1. Redistributions of source code must retain the above copyright notice, this list
|
|
|
9 |
# of conditions and the following disclaimer.
|
|
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright notice, this
|
|
|
11 |
# list of conditions and the following disclaimer in the documentation and/or other
|
|
|
12 |
# materials provided with the distribution.
|
|
|
13 |
# 3. Neither the name of Daily Data, Inc. nor the names of its contributors may be
|
|
|
14 |
# used to endorse or promote products derived from this software without specific
|
|
|
15 |
# prior written permission.
|
|
|
16 |
#
|
|
|
17 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
|
18 |
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
19 |
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
|
20 |
# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
21 |
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
22 |
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
|
23 |
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
24 |
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
|
25 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
|
26 |
# DAMAGE.
|
|
|
27 |
|
|
|
28 |
use strict;
|
|
|
29 |
use warnings;
|
|
|
30 |
|
|
|
31 |
use JSON qw( decode_json encode_json );
|
|
|
32 |
use Data::Dumper;
|
|
|
33 |
use lib '.';
|
|
|
34 |
use opnsense;
|
|
|
35 |
|
|
|
36 |
# default config file name if not specified on command line
|
|
|
37 |
my $configFileName = 'config.json';
|
|
|
38 |
my @fields = ( 'url', 'apiKey', 'apiSecret', 'ovpnIndex', 'template', 'hostname', 'localport' );
|
|
|
39 |
|
|
|
40 |
sub usage {
|
|
|
41 |
print "Usage: $0 <opnsense_config_file>\n";
|
|
|
42 |
exit 1;
|
|
|
43 |
}
|
|
|
44 |
|
|
|
45 |
# readConfig
|
|
|
46 |
# Reads in the configuration file and returns a hash ref of the data
|
|
|
47 |
# if file does not exist, returns empty hash ref
|
|
|
48 |
# file - name of file to read (default config.json if not specified)
|
|
|
49 |
sub readConfig {
|
|
|
50 |
my ( $file ) = @_;
|
|
|
51 |
my $data = {};
|
|
|
52 |
if ( -e $file ) {
|
|
|
53 |
open( my $fh, '<', $file ) or die "Cannot open $file: $!";
|
|
|
54 |
local $/; # slurp mode
|
|
|
55 |
my $json_text = <$fh>;
|
|
|
56 |
close $fh;
|
|
|
57 |
$data = decode_json($json_text);
|
|
|
58 |
}
|
|
|
59 |
return $data;
|
|
|
60 |
}
|
|
|
61 |
|
|
|
62 |
sub writeConfig {
|
|
|
63 |
my ( $file, $data ) = @_;
|
|
|
64 |
open( my $fh, '>', $file ) or die "Cannot open $file: $!";
|
|
|
65 |
print $fh encode_json($data);
|
|
|
66 |
close $fh;
|
|
|
67 |
}
|
|
|
68 |
|
|
|
69 |
sub trim {
|
|
|
70 |
my ($s) = @_;
|
|
|
71 |
$s =~ s/^\s+|\s+$//g if defined $s;
|
|
|
72 |
return $s;
|
|
|
73 |
}
|
|
|
74 |
|
|
|
75 |
sub menuSelect {
|
|
|
76 |
my ( $prompt, $options, $additionalText ) = @_;
|
|
|
77 |
print "$prompt\n";
|
|
|
78 |
for my $i ( 0 .. $#$options ) {
|
|
|
79 |
print " " . ($i+1) . ". " . $options->[$i] . ($additionalText->[$i] ? $additionalText->[$i] : "") . "\n";
|
|
|
80 |
}
|
|
|
81 |
print "Select option (1-" . ($#$options + 1) . "): ";
|
|
|
82 |
my $sel = <STDIN>;
|
|
|
83 |
chomp $sel;
|
|
|
84 |
if ( $sel !~ /^\d+$/ || $sel < 1 || $sel > ($#$options + 1) ) {
|
|
|
85 |
die "Invalid selection\n";
|
|
|
86 |
}
|
|
|
87 |
return $options->[$sel - 1];
|
|
|
88 |
}
|
|
|
89 |
|
|
|
90 |
sub editRouter {
|
|
|
91 |
my ( $config, $keys ) = @_;
|
|
|
92 |
my $option = '';
|
|
|
93 |
$config->{'template'} = 'PlainOpenVPN' unless defined $config->{'template'};
|
|
|
94 |
my $vpnProviders = ();
|
|
|
95 |
while ( $option ne 'Done' ) {
|
|
|
96 |
print "\nEditing router configuration:\n";
|
|
|
97 |
$option = &menuSelect(
|
|
|
98 |
"Select field to edit",
|
|
|
99 |
[ @$keys, 'Done' ],
|
|
|
100 |
[ map { defined $config->{$_} ? " (current: $config->{$_})" : " (not set)" } @$keys, '' ]
|
|
|
101 |
);
|
|
|
102 |
if ( $option ne 'Done' ) {
|
|
|
103 |
if ( $option eq 'ovpnIndex' ) {
|
|
|
104 |
# special handling for ovpnIndex to show available providers
|
|
|
105 |
if ( defined $config->{'url'} && defined $config->{'apiKey'} && defined $config->{'apiSecret'} ) {
|
|
|
106 |
my $opnsense = new opnsense(
|
|
|
107 |
url => $config->{'url'},
|
|
|
108 |
apiKey => $config->{'apiKey'},
|
|
|
109 |
apiSecret => $config->{'apiSecret'},
|
|
|
110 |
);
|
|
|
111 |
my $providers = $opnsense->get_vpn_providers();
|
|
|
112 |
if ( ref($providers) eq 'HASH' && keys %$providers ) {
|
| 15 |
rodolico |
113 |
#die Dumper( $providers );
|
| 14 |
rodolico |
114 |
my @providerIndex = sort keys %$providers;
|
|
|
115 |
my @additionalText = map {
|
|
|
116 |
defined $config->{'ovpnIndex'} &&
|
|
|
117 |
($_ eq $config->{'ovpnIndex'} ? " (current) " : "") . $providers->{$_}
|
|
|
118 |
} @providerIndex;
|
|
|
119 |
my $selectedProvider = &menuSelect( "Select VPN Provider", \@providerIndex, \@additionalText );
|
|
|
120 |
$config->{'ovpnIndex'} = $selectedProvider;
|
|
|
121 |
next; # skip the rest of the loop
|
|
|
122 |
} else {
|
|
|
123 |
print "No VPN providers found or error retrieving providers. Please check your connection details.\n";
|
|
|
124 |
}
|
|
|
125 |
} else {
|
|
|
126 |
print "Please set url, apiKey, and apiSecret before selecting ovpnIndex.\n";
|
|
|
127 |
next; # skip the rest of the loop
|
|
|
128 |
}
|
|
|
129 |
} else {
|
|
|
130 |
print "Enter value for $option: ";
|
|
|
131 |
my $value = <STDIN>;
|
|
|
132 |
chomp $value;
|
|
|
133 |
$value = trim($value);
|
|
|
134 |
$config->{$option} = $value;
|
|
|
135 |
}
|
|
|
136 |
}
|
|
|
137 |
}
|
|
|
138 |
return $config;
|
|
|
139 |
}
|
|
|
140 |
|
|
|
141 |
$configFileName = shift if defined shift;
|
|
|
142 |
|
|
|
143 |
my $config = &readConfig( $configFileName );
|
|
|
144 |
|
|
|
145 |
my $routerName = &menuSelect( "Select router to configure", [ keys %$config, 'Add new router' ] );
|
|
|
146 |
if ( $routerName eq 'Add new router' ) {
|
|
|
147 |
print "Enter new router name: ";
|
|
|
148 |
$routerName = <STDIN>;
|
|
|
149 |
chomp $routerName;
|
|
|
150 |
$routerName = trim($routerName);
|
|
|
151 |
$config->{$routerName} = {};
|
|
|
152 |
}
|
|
|
153 |
|
|
|
154 |
$config->{$routerName} = &editRouter( $config->{$routerName}, \@fields );
|
|
|
155 |
|
|
|
156 |
#print Dumper( $config );
|
|
|
157 |
|
|
|
158 |
writeConfig( $configFileName, $config );
|