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