working automatic prompting
[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 sub _sudo_perl_command {
24   my ($self) = @_;
25   return
26     'sudo', '-S', '-u', $self->target_user, '-p', "[sudo] password please\n",
27     'perl', '-MPOSIX=dup2',
28             '-e', 'print STDERR "GO\n"; exec(@ARGV);',
29     $self->_perl_command($self->target_user);
30 }
31
32 sub _start_perl {
33   my $self = shift;
34   my $sudo_stderr = gensym;
35   my $pid = open3(
36     my $foreign_stdin,
37     my $foreign_stdout,
38     $sudo_stderr,
39     $self->_sudo_perl_command(@_)
40   ) or die "open3 failed: $!";
41   chomp(my $line = <$sudo_stderr>);
42   if ($line eq "GO") {
43     # started already, we're good
44   } elsif ($line =~ /\[sudo\]/) {
45     my $cb = $self->password_callback;
46     die "sudo sent ${line} but we have no password callback"
47       unless $cb;
48     print $foreign_stdin $cb->($line, @_), "\n";
49     chomp($line = <$sudo_stderr>);
50     if ($line and $line ne 'GO') {
51       die "sent password and expected newline from sudo, got ${line}";
52     }
53     elsif (not $line) {
54       chomp($line = <$sudo_stderr>);
55       die "sent password but next line was ${line}"
56         unless $line eq "GO";
57     }
58   } else {
59     die "Got inexplicable line ${line} trying to sudo";
60   };
61   Object::Remote->current_loop
62                 ->watch_io(
63                     handle => $sudo_stderr,
64                     on_read_ready => sub {
65                       if (sysread($sudo_stderr, my $buf, 1024) > 0) {
66                         print STDERR $buf;
67                       } else {
68                         Object::Remote->current_loop
69                                       ->unwatch_io(
70                                           handle => $sudo_stderr,
71                                           on_read_ready => 1
72                                         );
73                       }
74                     }
75                   );
76   return ($foreign_stdin, $foreign_stdout, $pid);
77 };
78
79 no warnings 'once';
80
81 push @Object::Remote::Connection::Guess, sub {
82   for ($_[0]) {
83     # username followed by @
84     if (defined and !ref and /^ ([^\@]*?) \@ $/x) {
85       return __PACKAGE__->new(target_user => $1)->connect;
86     }
87   }
88   return;
89 };
90
91 1;