#!/usr/bin/perl -w #################################################### # backup-ironport-slbl v0.2.0 # by Brandon Lee Poyner bpoyner@ccac.edu # # This script will back up the IronPort Safelist # and Blocklist. # # Requirements: # 1) the perl modules listed below # 2) an Admin level account on the appliance # 3) the SSH or FTP service to be enabled. # 3a) a RSA or DSA key if using SCP #################################################### use strict; use WWW::Mechanize; use HTTP::Cookies; use Crypt::SSLeay; use Net::SCP; use Net::FTP; use Getopt::Long qw(:config no_ignore_case); use File::Path; use File::Basename; use POSIX qw(strftime); my %hosts = ( # Each host can be defined here. Default values will be copied # from %host_defaults if not defined here. 'my.ironport.hostname' => { 'web_proto' => 'https', 'web_port' => 443, 'admin_password' => 'secure', }, #'10.0.0.1' => { # 'fetch_method' => 'scp', #}, ); my %host_defaults = ( 'web_proto' => 'http', 'web_port' => 80, 'admin_username' => 'administrator', 'admin_password' => 'password', 'fetch_method' => 'ftp', 'config_dir_on_appliance' => '/configuration', 'base_backup_dir' => '/var/lib/ironport/config', 'sleep_period' => 5, 'retries' => 10, 'webpages' => { login => 'login', config_save => 'system_administration/configuration_file', config_status => '?action=PollDatabaseStatus', logout => 'login?action=Logout' }, ); my %settings = ( 'debug' => 0, 'ym_date' => strftime("%Y%m", localtime), 'prog_name' => basename($0), ); &main; exit 0; sub main { &parse_options; &push_defaults; &sanity_check; for my $host ( keys %hosts ) { &form_urls($host); my $retval=&save_configuration($host); if ($retval == 0) { &fetch_configuration($host); } } my $retval=&report; if ($retval == 1) { exit 1; } } sub parse_options { GetOptions('h|help' => \my $help, 'debug' => \$settings{debug}, 'H|hosts=s' => \my $hosts, 'port=i' => \my $port, 'proto=s' => \my $proto, 'sleep=i' => \my $sleep, 'fetch=s' => \my $fetch, 'u|username=s' => \my $user, 'p|password=s' => \my $pass, 'd|dir=s' => \my $backup_dir) || exit 1; if (defined($help) && ($help ne "")) { &print_usage; exit 1; } if (defined($hosts) && ($hosts ne "")) { my @hosts=split(/ /,$hosts); for my $host (@hosts) { $hosts{$host} = (); } for my $server ( keys %hosts ) { if (!(grep(/$server/,@hosts))) { delete($hosts{$server}); } } } for my $host ( keys %hosts ) { if (defined($port) && ($port ne "")) { $hosts{$host}->{web_port} = $port; } if (defined($proto) && ($proto ne "")) { $hosts{$host}->{web_proto} = $proto; } if (defined($fetch) && ($fetch ne "")) { $hosts{$host}->{fetch_method} = $fetch; } if (defined($user) && ($user ne "")) { $hosts{$host}->{admin_username} = $user; } if (defined($pass) && ($pass ne "")) { $hosts{$host}->{admin_password} = $pass; } if (defined($sleep) && ($sleep ne "")) { $hosts{$host}->{sleep_period} = $sleep; } if (defined($backup_dir) && ($backup_dir ne "")) { $hosts{$host}->{base_backup_dir} = $backup_dir; } } } sub print_usage { print <{url}->{base} = sprintf("%s://%s:%s/", $hosts{$host}->{web_proto},$host,$hosts{$host}->{web_port}); $hosts{$host}->{url}->{login_url} = sprintf("%s%s", $hosts{$host}->{url}->{base}, $hosts{$host}->{webpages}->{login}); $hosts{$host}->{url}->{config_url} = sprintf("%s%s", $hosts{$host}->{url}->{base}, $hosts{$host}->{webpages}->{config_save}); $hosts{$host}->{url}->{status_url} = sprintf("%s%s", $hosts{$host}->{url}->{config_url}, $hosts{$host}->{webpages}->{config_status}); $hosts{$host}->{url}->{logout_url} = sprintf("%s%s", $hosts{$host}->{url}->{base}, $hosts{$host}->{webpages}->{logout}); print < Base: $hosts{$host}->{url}->{base} Login: $hosts{$host}->{url}->{login_url} Configuration: $hosts{$host}->{url}->{config_url} Status: $hosts{$host}->{url}->{status_url} Logout: $hosts{$host}->{url}->{logout_url} EOF } sub sanity_check { for my $host ( keys %hosts ) { if ( ! -d $hosts{$host}->{base_backup_dir} ) { print "Error: backup directory $hosts{$host}->{base_backup_dir} does not exist\n"; exit 1; } } if (int(keys(%hosts)) == 0) { print "Error: No hosts defined!\n"; exit 1; } } sub push_defaults { for my $host ( keys %hosts ) { for my $default ( keys %host_defaults ) { if (!(defined($hosts{$host}->{$default}))) { $hosts{$host}->{$default} = $host_defaults{$default}; } } for my $default ( keys %{$host_defaults{webpages}} ) { if (!(defined($hosts{$host}->{webpages}->{$default}))) { $hosts{$host}->{webpages}->{$default} = $host_defaults{webpages}->{$default}; } } $hosts{$host}->{backup_dir} = sprintf("%s/%s/%s", $hosts{$host}->{base_backup_dir}, $host, $settings{ym_date}); my $password = mask($hosts{$host}->{admin_password}); print < Host: $host Web Proto: $hosts{$host}->{web_proto} Web Port: $hosts{$host}->{web_port} User: $hosts{$host}->{admin_username} Password: $password Fetch Method: $hosts{$host}->{fetch_method} Backup Dir: $hosts{$host}->{backup_dir} Sleep Period: $hosts{$host}->{sleep_period} Retries: $hosts{$host}->{retries} EOF } } sub save_configuration { my ($host) = @_; my $mech = WWW::Mechanize->new(); $mech->cookie_jar(HTTP::Cookies->new()); $mech->get($hosts{$host}->{url}->{login_url}); if ($mech->content !~ /Username:/) { $mech->follow_link( url_regex => qr/login/i); } unless ($mech->success) { $hosts{$host}->{error} = "Could not get login page on $hosts{$host}->{url}->{base}"; return 1; } $mech->form_name('login'); $mech->field(username => $hosts{$host}->{admin_username}); $mech->field(password => $hosts{$host}->{admin_password}); $mech->click(); unless ($mech->success) { $hosts{$host}->{error} = "Failed login to $hosts{$host}->{url}->{base}"; return 1; } $mech->get($hosts{$host}->{url}->{config_url}); unless ($mech->success) { $hosts{$host}->{error} = "Could not get $hosts{$host}->{url}->{config_url}"; return 1; } $mech->form_name('form'); { # hidden field, turn off warnings from WWW::Mechanize local $^W = 0; $mech->field('action' => 'Export'); } $mech->click(); unless ($mech->success) { $hosts{$host}->{error} = "Could not export SLBL at $hosts{$host}->{url}->{config_url}"; return 1; } for (my $attempt=0; $attempt < $hosts{$host}->{retries} ; $attempt++) { sleep $hosts{$host}->{sleep_period}; $mech->get($hosts{$host}->{url}->{status_url}); if ($mech->content =~ /(slbl.*\.csv)/) { $hosts{$host}->{slbl_file}=$1; last; } } $mech->get($hosts{$host}->{url}->{logout_url}); unless ($mech->success) { $hosts{$host}->{error} = "Could not logout"; return 1; } if ($hosts{$host}->{slbl_file} eq "") { $hosts{$host}->{error} = "Could not determine slbl filename"; return 1; } return 0; } sub fetch_configuration { my ($host) = @_; print < Host: $host Fetch Method: $hosts{$host}->{fetch_method} Host Directory: $hosts{$host}->{config_dir_on_appliance} SLBL File: $hosts{$host}->{slbl_file} Backup Directory: $hosts{$host}->{backup_dir} EOF if ( ! -d "$hosts{$host}->{backup_dir}" ) { mkpath([$hosts{$host}->{backup_dir}],0,0700) || ($hosts{$host}->{error} = "Could not make directory $hosts{$host}->{backup_dir}", return 1); } for (my $attempt=0; $attempt < $hosts{$host}->{retries} ; $attempt++) { my $retval; sleep $hosts{$host}->{sleep_period}; if ($hosts{$host}->{fetch_method} eq "ftp") { $retval=&ftp_fetch_configuration($host); } else { $retval=&scp_fetch_configuration($host); } if ($retval == 0) { last; } } if ( ! -e "$hosts{$host}->{backup_dir}/$hosts{$host}->{slbl_file}" ) { $hosts{$host}->{error} = "backup file $hosts{$host}->{slbl_file} not found in $hosts{$host}->{backup_dir}"; return 1; } return 0; } sub ftp_fetch_configuration { my ($host) = @_; my $ftp=Net::FTP->new($host,Timeout=>60) || (&debug("FTP: Could not connect to $host"), return 1); $ftp->login($hosts{$host}->{admin_username},$hosts{$host}->{admin_password}) || (&debug("FTP: login failed to $host for $hosts{$host}->{admin_username}"), return 1); $ftp->cwd($hosts{$host}->{config_dir_on_appliance}) || (&debug("FTP: Could not cwd to $hosts{$host}->{config_dir_on_appliance} on $host"), return 1); $ftp->get($hosts{$host}->{slbl_file},"$hosts{$host}->{backup_dir}/$hosts{$host}->{slbl_file}") || (&debug("FTP: Could not get $hosts{$host}->{slbl_file} from $host"), return 1); return 0; } sub scp_fetch_configuration { my ($host) = @_; my ($full_path) = $hosts{$host}->{config_dir_on_appliance} . '/' . $hosts{$host}->{slbl_file}; my $scp = Net::SCP->new({ "host"=>$host, "user"=>$hosts{$host}->{admin_username} }) || (&debug("SCP: Could not connect to $host"), return 1); $scp->get($full_path, $hosts{$host}->{backup_dir}) || (&debug("SCP: Could not get $full_path on $host"), return 1); $scp->quit; return 0; } sub report { my $errors = 0; for my $host ( keys %hosts ) { if (defined($hosts{$host}->{error})) { print "Error: $hosts{$host}->{error}\n"; $errors++; } if ($settings{debug} == 1) { my $result; if (defined($hosts{$host}->{error})) { $result = "Error: $hosts{$host}->{error}"; } else { $result = sprintf("SLBL File: %s/%s", $hosts{$host}->{backup_dir}, $hosts{$host}->{slbl_file}); } print < $result EOF } } if ($errors != 0) { return 1; } return 0; } sub debug { my ($message) = @_; if (defined($settings{debug}) && ($settings{debug} != 0)) { printf STDERR "%s\n",$message; } } sub mask { return "xxxxxxxx"; }