File Coverage

blib/lib/Object/Remote/Connector/LocalSudo.pm
Criterion Covered Total %
statement 18 53 33.9
branch 0 16 0.0
condition 0 3 0.0
subroutine 6 11 54.5
pod n/a
total 24 83 28.9


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