Subversion Repositories sysadmin_scripts

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
191 rodolico 1
#! /usr/bin/env perl
2
 
3
=head1 NAME
4
 
5
backdoor - Network Backdoor Management Script
6
 
7
=head1 SYNOPSIS
8
 
9
    backdoor [options]            # Check remote sites and bring up/down accordingly
10
    backdoor [options] up         # Manually bring up the backdoor interface
11
    backdoor [options] down       # Manually bring down the backdoor interface
12
 
13
    Options:
14
      --test              Test mode - show commands without executing them
15
      --verbose           Display interface status after changes
16
      --help              Show this help message
17
 
18
=head1 DESCRIPTION
19
 
20
This script manages a network backdoor interface by checking remote sites for
21
specific MD5 checksums. If a matching checksum is found, the backdoor interface
22
is brought up with configured routes. If no match is found, the interface is
23
brought down.
24
 
25
The script can also be run with manual 'up' or 'down' commands to directly
26
control the backdoor interface without checking remote sites.
27
 
28
Configuration is managed via a YAML file, which will be created with default
29
values if it doesn't exist on first run.
30
 
31
=head1 CONFIGURATION
32
 
33
The configuration file is located at: ./backdoor.conf.yaml
34
 
35
If the configuration file does not exist, it will be created with default values
36
on first run. You must edit this file with your specific network settings before
37
using the script.
38
 
39
Configuration options:
40
  - TEST:            Set to 1 to enable test mode (show commands without executing)
41
                     Also changes configuration file to cwd/backdoor.conf.yaml
42
  - verbose:         Set to 1 to display interface status after changes
43
  - ip:              IP address to assign to the backdoor interface
44
  - netmask:         Network mask for the interface
45
  - nic:             Network interface card name (e.g., eth0, eth1)
46
  - gateway:         Gateway/router IP for routing
47
  - allowedSubnets:  Hash of allowed subnets/IPs with netmask and ports
48
                     Key is the subnet/IP (can be DNS name), value contains:
49
                       - netmask: CIDR notation (e.g., 24, 32)
50
                       - ports: Port numbers to allow
51
  - sitesToCheck:    Hash of URLs to check with their expected MD5 checksums
52
                     Key is the URL, value is the expected checksum
53
 
54
Note: Command-line options (--test, --verbose) override configuration file settings.
55
 
56
=head1 REQUIREMENTS
57
 
58
  - Perl 5.x
59
  - YAML::Tiny module
60
  - Getopt::Long module (core Perl)
61
  - Pod::Usage module (core Perl)
62
  - wget command-line tool
63
  - ifconfig and ip route commands (iproute2 package)
64
  - Root/sudo privileges for network configuration
65
 
66
=head1 EXAMPLES
67
 
68
  # Run in test mode to see what commands would be executed
69
  backdoor --test
70
 
71
  # Check remote sites and manage interface accordingly with verbose output
72
  backdoor --verbose
73
 
74
  # Manually bring up the interface without checking remote sites
75
  backdoor up
76
 
77
  # Manually bring down the interface in test mode
78
  backdoor --test down
79
 
80
  # Show help documentation
81
  backdoor --help
82
 
83
=head1 OPERATION
84
 
85
The script operates in two modes:
86
 
87
1. Automatic Mode (default): Checks configured remote sites for MD5 checksums.
88
   If a matching checksum is found, the backdoor interface is brought up.
89
   Otherwise, it is brought down.
90
 
91
2. Manual Mode: When 'up' or 'down' is specified as an argument, the script
92
   directly controls the interface without checking remote sites.
93
 
94
The script is idempotent - it will not attempt to bring up an interface that
95
is already up, or bring down an interface that is already down.
96
 
97
=head1 LICENSE
98
 
99
Copyright (c) 2025, R. W. Rodolico
100
All rights reserved.
101
 
102
Redistribution and use in source and binary forms, with or without
103
modification, are permitted provided that the following conditions are met:
104
 
105
1. Redistributions of source code must retain the above copyright notice, this
106
   list of conditions and the following disclaimer.
107
 
108
2. Redistributions in binary form must reproduce the above copyright notice,
109
   this list of conditions and the following disclaimer in the documentation
110
   and/or other materials provided with the distribution.
111
 
112
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
113
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
114
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
115
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
116
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
117
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
118
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
119
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
120
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
121
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
122
 
123
=head1 AUTHOR
124
 
125
R. W. Rodolico <rodo@dailydata.net>
126
 
127
=head1 VERSION
128
 
129
Version 1.0.0 - December 2025
130
 
131
=cut
132
 
133
use strict;
134
use warnings;
135
use YAML::Tiny;
136
use Getopt::Long;
137
use Pod::Usage;
138
 
139
my $configFile = './backdoor.conf.yaml';  # Path to YAML configuration file
140
 
141
# Default configuration
142
# $config is a hashref used globally by routines; no parameters are passed in many cases
143
my $config = {
144
   TEST        => 0,         # Set to 1 to test without executing commands
145
   verbose     => 0,         # Set to 1 to display interface status after changes
146
   ip          => '0.0.0.0', # the IP to assign when bringing up
147
   netmask     => '255.255.255.0', # the netmask to use
148
   nic         => 'eth1', # the network interface card to use
149
   gateway     => '0.0.0.0', # the upstream router/gateway
150
   # Allowed Subnet/IPs for backdoor access. The value is what ports to open.
151
   # may also be a DNS resolvable URL (uses realIP subroutine to resolve)
152
   allowedSubnets  => {
153
      '0.0.0.0' => { netmask => 24, ports => '22' },
154
   },
155
   # key is site to check via wget, value is the expected md5 checksum
156
   sitesToCheck       => {
157
      'https://example.com/key1' => 'f9620d8865581a9402a49e25b3504ff7',
158
      'https://example.org/key2' => 'a196c22275cd5e54400bd63d5954cf01',
159
   },
160
};
161
 
162
# Subroutines for network management
163
 
164
# determines the real IP address of a domain name or returns the IP if given an IP
165
# Uses the 'host' command to resolve domain names to IP addresses
166
#
167
# Parameters:
168
#   $ip - an IP address or domain name
169
#
170
# Returns: the resolved IP address or an empty string on failure
171
sub realIP {
172
   my $ip = shift;
173
   # if it is a real IP address, just return it
174
   return $ip if $ip =~ m/^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
175
   # Otherwise, use host to grab the IP address
176
   $ip = `host -t A $ip | rev | cut -d' ' -f1 | rev`;
177
   chomp $ip;
178
   return $ip;
179
}
180
 
181
# checks if the backdoor interface is currently up
182
# Searches for the configured IP in the interface output
183
#
184
# Returns: 1 if up, 0 if down
185
sub currentStatus {
186
   my $out = `ifconfig $config->{nic} | grep $config->{ip}`;
187
   return $out =~ m/$config->{ip}/;
188
}
189
 
190
# runs a list of commands
191
# Executes system commands, respecting the $config->{TEST} flag
192
#
193
# Parameters:
194
#   @commands - list of shell commands to execute
195
#
196
# Returns: nothing
197
sub runCommand {
198
   while ( my $command = shift ) {
199
      print "$command\n" if $config->{TEST};
200
      `$command` unless $config->{TEST};
201
   }
202
}
203
 
204
# brings up the backdoor interface if it is down
205
# Configures the network interface with the specified IP/netmask and
206
# adds routes for all allowed subnets through the configured gateway
207
#
208
# Returns: nothing (exits early if already up)
209
sub bringUp {
210
   my $currentStatus = &currentStatus();
211
   return if $currentStatus; # Already up
212
 
213
   print "Open Sesame\n";
214
   # configure the interface and bring it up
215
   &runCommand( "ifconfig $config->{nic} $config->{ip} netmask $config->{netmask}" );
216
   foreach my $allowedSubnet ( keys %{$config->{allowedSubnets}} ) {
217
      my $subnetIP = &realIP( $allowedSubnet );
218
      my $netmask = $config->{allowedSubnets}->{$allowedSubnet}->{netmask} || 32;
219
      $subnetIP .= "/$netmask";
220
      &runCommand( "ip route add $subnetIP via $config->{gateway} dev $config->{nic}" );
221
   }
222
   # display the status
223
   &runCommand( "ifconfig $config->{nic}" ) if $config->{verbose};
224
   &runCommand( "route -n" ) if $config->{verbose};
225
 
226
}
227
 
228
# brings down the backdoor interface if it is up
229
# Removes all configured routes and brings down the network interface
230
#
231
# Returns: nothing (exits early if already down)
232
sub bringDown {
233
   my $currentStatus = &currentStatus();
234
   return unless $currentStatus; # Already down
235
 
236
   print "You shall not pass!\n";
237
   # first, remove the routes
238
   foreach my $allowedSubnet ( keys %{$config->{allowedSubnets}} ) {
239
      my $subnetIP = &realIP( $allowedSubnet );
240
      my $netmask = $config->{allowedSubnets}->{$allowedSubnet}->{netmask} || 32;
241
      $subnetIP .= "/$netmask";
242
      &runCommand( "ip route del $subnetIP" );
243
   }
244
   # reset and bring down the interface
245
   &runCommand( "ifconfig $config->{nic} 192.168.1.1 netmask 255.255.255.255" );
246
   &runCommand( "ifconfig $config->{nic} down" );
247
   # display the status
248
   &runCommand( "ifconfig $config->{nic}" ) if $config->{verbose};
249
   &runCommand( "route -n" ) if $config->{verbose};
250
}
251
 
252
# Subroutine to check remote sites
253
# Checks each URL in the sitesToCheck hash for its expected MD5 checksum
254
# 
255
# Parameters:
256
#   $sitesToCheck - hashref of URL => expected_md5_checksum
257
#
258
# Returns:
259
#   1 if any site returns the expected checksum
260
#   0 if no matches are found
261
sub checkRemote {
262
   my ( $sitesToCheck ) = @_;
263
 
264
   foreach my $url ( keys %$sitesToCheck ) {
265
      my $expectedChecksum = $sitesToCheck->{$url};
266
      my $output = `wget -q -O - $url | md5sum`;
267
      $output =~ m/^([a-f0-9]+)/i;
268
      if ( $1 eq $expectedChecksum ) {
269
         return 1; # Match found
270
      }
271
   }
272
 
273
   return 0; # No matches
274
}
275
 
276
#==============================================================================
277
# Main Program Logic
278
#==============================================================================
279
 
280
# Read config file or create it with defaults if it doesn't exist
281
# Path to YAML configuration file. It can be in cwd or /etc/backdoor/
282
$configFile = -f './backdoor.conf.yaml' ? './backdoor.conf.yaml' : '/etc/backdoor/backdoor.conf.yaml';
283
if ( -e $configFile ) {
284
   # Config file exists, read it
285
   my $yaml = YAML::Tiny->read( $configFile );
286
   my $fileConfig = $yaml->[0];
287
   # Merge file config with current config
288
   foreach my $key ( keys %$fileConfig ) {
289
      $config->{$key} = $fileConfig->{$key};
290
   }
291
} else {
292
   # Config file doesn't exist, create it with defaults
293
   my $yaml = YAML::Tiny->new( $config );
294
   makedir( '/etc/backdoor', 0755 ) unless -d '/etc/backdoor';
295
   $yaml->write( $configFile );
296
   die "Created default config file at $configFile. Please edit it and rerun.\n";
297
}
298
 
299
# Parse command-line options (these override config file settings)
300
my $help = 0;
301
GetOptions(
302
   'test'     => \$config->{TEST},
303
   'verbose'  => \$config->{verbose},
304
   'help|?'   => \$help,
305
) or pod2usage(2);
306
 
307
pod2usage(1) if $help;
308
 
309
# Validate that all required configuration fields are present
310
die "Configuration file $configFile is missing required fields.\n"
311
   unless exists $config->{ip}
312
      && exists $config->{netmask}
313
      && exists $config->{nic}
314
      && exists $config->{gateway}
315
      && exists $config->{sitesToCheck};
316
 
317
# Process command-line argument if provided
318
my $command = $ARGV[0] ? lc shift : '';
319
if ( $command eq 'up' ) {
320
   &bringUp();
321
   exit 0;
322
} elsif ( $command eq 'down' ) {
323
   &bringDown();
324
   exit 0;
325
}
326
 
327
# Check remote sites and manage backdoor interface accordingly
328
if ( checkRemote( $config->{sitesToCheck} ) ) {
329
   &bringUp();
330
   exit 0;
331
} else {
332
   &bringDown();
333
   exit 0;
334
}
335
 
336
1;