package MyOpenSSH 0.000001; use Carp; use Data::Dumper; use Moose::Role; use Modern::Perl; use Net::OpenSSH; use Params::Validate; with 'MyLogger2'; has 'ssh' => (is => 'rw', isa => 'Net::OpenSSH', required => 1, lazy => 0, handles => qr/.*/, ); has 'connection' => (is => 'ro', isa => 'Str', required => 1, lazy => 0); around BUILDARGS => sub { my $orig = shift; my $class = shift; my %args = ref $_[0] ? %{$_[0]} : @_; croak 'a host must be supplied for ssh: ssh => (\'@\', %opts)' if !%args; my ($host) = $args{ssh}; if (ref $host eq 'Net::OpenSSH') { my $user = $host->get_user; my $host = $host->get_host; $args{connection} = $user . '@' . $host; return $class->$orig( %args ); } delete $args{ssh}; my %ssh_args = map { $_ => $args{$_ } } grep { exists $args{$_ } } qw( user port password passphrase key_path gateway proxy_command batch_mode ctl_dir ssh_cmd scp_cmd rsync_cmd remote_shell timeout kill_ssh_on_timeout strict_mode async connect master_opts default_ssh_opts forward_agent forward_X11 default_stdin_fh default_stdout_fh default_stderr_fh default_stdin_file default_stdout_file default_stderr_file master_stdout_fh master_stderr_fh master_stdout_discard master_stderr_discard expand_vars vars external_master default_encoding default_stream_encoding default_argument_encoding password_prompt login_handler master_setpgrp); my $ssh = Net::OpenSSH->new($host, %ssh_args); $ssh->error and croak "could not connect to host: $ssh->error"; return $class->$orig( ssh => $ssh, %args, connection => $ssh->get_user . '@' . $ssh->get_host); }; # check to make sure the command can't do anything destructive on the server sub _check_command { my $self = shift; my @cmds = @_; shift @cmds if ref $cmds[0]; my @restricted_cmds = qw (rm chmod chown chgrp); my $restricted_cmds = join '|', map { "($_)" } @restricted_cmds; my $restricted_cmd_regex = qr/\b$restricted_cmds\s+/; # scan args to see if they have a restricted command my $is_restricted = grep { $_ =~ $restricted_cmd_regex } @cmds; return if !$is_restricted; # put command in one string my $cmd = ''; if (@cmds > 1) { my $cmd = join ' ', @cmds; } else { $cmd = shift @cmds; } # chop off sudo commands $cmd =~ s/^\bsu(do)*\s+//gi; my @multiple_cmds = split /&&/, $cmd; @multiple_cmds = map { split /;/ } @multiple_cmds; my @safe_dirs = qw (/var/www/ /Users/me/tmp/ /home/me/tmp/ /home/sites/wp_sites/ /home/dir/preview); my @safe_dir_regexes = map { s/\//\\\//g; qr/$_.+/; } @safe_dirs; foreach $cmd (@multiple_cmds) { if ($cmd =~ $restricted_cmd_regex) { if (! grep { $cmd =~ /$_/} @safe_dir_regexes ) { $self->logf("Not authorized to perform that operation in that directory: $cmd"); } if ($cmd =~ /\s(\/|\b)[^\s]*\/[^\s]*(\/|\b)\s+(\/|\b).*\b/) { $self->logf('Cannot run restricted commands on more than file or directory at a time.'); } if ($cmd =~ /(\s\*)|(\s\*$)|(\s\/\s)|(\s\/$)|(\s\/\s)|(\s\/$)|(\s\.\.)|(\s\.\.$)/) { $self->logf("Not authorized to perform that operation in that directory: $cmd"); } } } } sub _file_exists { my $self = shift; my $file = shift; return $self->grab(_one_liner("-f('$file')")); } sub _dir_exists { my $self = shift; my $dir = shift; return $self->grab(_one_liner("-d('$dir')")); } sub _one_liner { my $cmd = shift; my $mod = shift; my $cd = shift; my $one_liner = 'perl '; $one_liner .= $mod ? "-M$mod -e " : '-e'; $one_liner = $cd ? "cd $cd && " . $one_liner : $one_liner; return qq#$one_liner "print ($cmd)"#; } # wrapper for system method sub exec { my $self = shift; $self->_check_command(@_); return $self->ssh->system(@_) if !$self->ssh->error; croak ('ssh command failed: ' . $self->ssh->error . ", $!"); } # wrapper for capture method sub grab { my $self = shift; $self->_check_command(@_); return $self->ssh->capture(@_) if !$self->ssh->error; croak ('ssh command failed: ' . $self->ssh->error); } sub disconnect { my $self = shift; $self->ssh->disconnect; }