aa19ced3fbcbc1dcef95ba9c093af24235cb68f1
[scpubgit/Object-Remote.git] / lib / Object / Remote / Connector / LocalSudo.pm
1 package Object::Remote::Connector::LocalSudo;
2
3 use Symbol qw(gensym);
4 use IPC::Open3;
5 use Moo;
6
7 extends 'Object::Remote::Connector::Local';
8
9 has password_callback => (is => 'ro');
10
11 sub _sudo_perl_command {
12   my ($self, $target_user) = @_;
13   return
14     'sudo', '-S', '-u', $target_user, '-p', "[sudo] password please\n",
15     'perl', '-MPOSIX=dup2',
16             '-e', 'print STDERR "GO\n"; exec(@ARGV);',
17     $self->_perl_command($target_user);
18 }
19
20 sub _start_perl {
21   my $self = shift;
22   my $sudo_stderr = gensym;
23   my $pid = open3(
24     my $foreign_stdin,
25     my $foreign_stdout,
26     $sudo_stderr,
27     $self->_sudo_perl_command(@_)
28   ) or die "open3 failed: $!";
29   chomp(my $line = <$sudo_stderr>);
30   if ($line eq "GO") {
31     # started already, we're good
32   } elsif ($line =~ /\[sudo\]/) {
33     my $cb = $self->password_callback;
34     die "sudo sent ${line} but we have no password callback"
35       unless $cb;
36     print $foreign_stdin $cb->($line, @_), "\n";
37     chomp($line = <$sudo_stderr>);
38     if ($line and $line ne 'GO') {
39       die "sent password and expected newline from sudo, got ${line}";
40     }
41     elsif (not $line) {
42       chomp($line = <$sudo_stderr>);
43       die "sent password but next line was ${line}"
44         unless $line eq "GO";
45     }
46   } else {
47     die "Got inexplicable line ${line} trying to sudo";
48   };
49   Object::Remote->current_loop
50                 ->watch_io(
51                     handle => $sudo_stderr,
52                     on_read_ready => sub {
53                       if (sysread($sudo_stderr, my $buf, 1024) > 0) {
54                         print STDERR $buf;
55                       } else {
56                         Object::Remote->current_loop
57                                       ->unwatch_io(
58                                           handle => $sudo_stderr,
59                                           on_read_ready => 1
60                                         );
61                       }
62                     }
63                   );
64   return ($foreign_stdin, $foreign_stdout, $pid);
65 };
66
67 no warnings 'once';
68
69 push @Object::Remote::Connection::Guess, sub {
70   for ($_[0]) {
71     # username followed by @
72     if (defined and !ref and /^ ([^\@]*?) \@ $/x) {
73       return __PACKAGE__->new->connect($1);
74     }
75   }
76   return;
77 };
78
79 1;