File Coverage

blib/lib/Object/Remote/Connector/LocalSudo.pm
Criterion Covered Total %
statement 18 54 33.3
branch 0 16 0.0
condition 0 3 0.0
subroutine 6 11 54.5
pod n/a
total 24 84 28.5


line stmt bran cond sub pod time code
1             package Object::Remote::Connector::LocalSudo;
2              
3 11     11   10084 use Object::Remote::Logging qw (:log :dlog);
  11         30  
  11         116  
4 11     11   93 use Symbol qw(gensym);
  11         24  
  11         1492  
5 11     11   74 use Module::Runtime qw(use_module);
  11         21  
  11         90  
6 11     11   603 use IPC::Open3;
  11         25  
  11         677  
7 11     11   88 use Moo;
  11         19  
  11         73  
8              
9             extends 'Object::Remote::Connector::Local';
10              
11             has target_user => (is => 'ro', required => 1);
12              
13             has password_callback => (is => 'lazy');
14              
15             sub _build_password_callback {
16 0     0   0 my ($self) = @_;
17 0         0 my $pw_prompt = use_module('Object::Remote::Prompt')->can('prompt_pw');
18 0         0 my $user = $self->target_user;
19             return sub {
20 0     0   0 $pw_prompt->("sudo password for ${user}", undef, { cache => 1 })
21             }
22 0         0 }
23              
24             has sudo_perl_command => (is => 'lazy');
25              
26             sub _build_sudo_perl_command {
27 0     0   0 my ($self) = @_;
28             return [
29             'sudo', '-S', '-u', $self->target_user, '-p', "[sudo] password please\n",
30             'perl', '-MPOSIX=dup2',
31             '-e', 'print STDERR "GO\n"; exec(@ARGV);',
32 0         0 @{$self->perl_command},
  0         0  
33             ];
34             }
35              
36             sub _start_perl {
37 0     0   0 my $self = shift;
38 0         0 my $sudo_stderr = gensym;
39             my $pid = open3(
40             my $foreign_stdin,
41             my $foreign_stdout,
42             $sudo_stderr,
43 0 0       0 @{$self->sudo_perl_command}
  0         0  
44             ) or die "open3 failed: $!";
45 0         0 chomp(my $line = <$sudo_stderr>);
46 0 0       0 if ($line eq "GO") {
    0          
47             # started already, we're good
48             } elsif ($line =~ /\[sudo\]/) {
49 0         0 my $cb = $self->password_callback;
50 0 0       0 die "sudo sent ${line} but we have no password callback"
51             unless $cb;
52 0         0 print $foreign_stdin $cb->($line, @_), "\n";
53 0         0 chomp($line = <$sudo_stderr>);
54 0 0 0     0 if ($line and $line ne 'GO') {
    0          
55 0         0 die "sent password and expected newline from sudo, got ${line}";
56             }
57             elsif (not $line) {
58 0         0 chomp($line = <$sudo_stderr>);
59 0 0       0 die "sent password but next line was ${line}"
60             unless $line eq "GO";
61             }
62             } else {
63 0         0 die "Got inexplicable line ${line} trying to sudo";
64             };
65             Object::Remote->current_loop
66             ->watch_io(
67             handle => $sudo_stderr,
68             on_read_ready => sub {
69 0     0   0 Dlog_debug { "LocalSudo: Preparing to read data from $_" } $sudo_stderr;
  0         0  
70 0 0       0 if (sysread($sudo_stderr, my $buf, 32768) > 0) {
71 0         0 log_trace { "LocalSudo: successfully read data, printing it to STDERR" };
  0         0  
72 0         0 print STDERR $buf;
73 0         0 log_trace { "LocalSudo: print() to STDERR is done" };
  0         0  
74             } else {
75 0         0 log_debug { "LocalSudo: received EOF or error on file handle, unwatching it" };
  0         0  
76 0         0 Object::Remote->current_loop
77             ->unwatch_io(
78             handle => $sudo_stderr,
79             on_read_ready => 1
80             );
81             }
82             }
83 0         0 );
84 0         0 return ($foreign_stdin, $foreign_stdout, $pid);
85             };
86              
87 11     11   24899 no warnings 'once';
  11         25  
  11         2623  
88              
89             push @Object::Remote::Connection::Guess, sub {
90             for ($_[0]) {
91             # username followed by @
92             if (defined and !ref and /^ ([^\@]*?) \@ $/x) {
93             shift(@_);
94             return __PACKAGE__->new(@_, target_user => $1);
95             }
96             }
97             return;
98             };
99              
100             1;