File Coverage

blib/lib/Unix/Sudo.pm
Criterion Covered Total %
statement 18 47 38.3
branch 0 12 0.0
condition 0 3 0.0
subroutine 6 7 85.7
pod 1 1 100.0
total 25 70 35.7


line stmt bran cond sub pod time code
1             package Unix::Sudo;
2              
3 5     5   386603 use strict;
  5         53  
  5         146  
4 5     5   26 use warnings;
  5         7  
  5         381  
5              
6             our $VERSION = '1';
7              
8             require Exporter;
9             our @ISA = qw(Exporter);
10             our @EXPORT_OK = qw(sudo);
11              
12 5     5   32 use B::Deparse;
  5         10  
  5         145  
13 5     5   2959 use Data::Dumper;
  5         33256  
  5         327  
14 5     5   2098 use PadWalker qw(peek_my);
  5         3299  
  5         286  
15 5     5   2078 use Probe::Perl;
  5         5632  
  5         2231  
16              
17             =head1 NAME
18              
19             Unix::Sudo - run a block as root
20              
21             =head1 SYNOPSIS
22              
23             As a normal user who can C ...
24              
25             use Unix::Sudo qw(sudo);
26              
27             print `whoami`; # shows your normal username
28             sudo {
29             eval "no tainting";
30             print `whoami`; # root
31             };
32             print `whoami`; # back to normal
33              
34             =head1 EXPORTS / FUNCTIONS
35              
36             There is one function, which can be exported if you wish but is not exported by
37             default:
38              
39             =head2 sudo
40              
41             Takes a code-ref as its only argument. This can be in a variable, or as an
42             anonymous code-ref, or a block:
43              
44             my $code = sub { 1 + 2 };
45             sudo($code);
46              
47             sudo(sub { 1 + 2 });
48              
49             sudo { 1 + 2 };
50              
51             That code-ref will be executed as root, with no arguments, and with as much as
52             possible of the calling code's environment. It may return an integer value from
53             0 to 254. Anything else will be numified.
54              
55             If you want to return something more complicated then I recommend that you
56             return it on STDOUT and use L to retrieve it.
57              
58             =head1 ERRORS
59              
60             A return value of 255 is special and is used to indicate that the code couldn't
61             be compiled for some reason. When this happens the child process will spit an
62             error message to STDERR, and the parent will die and attempt to tell you where
63             you passed dodgy code to Unix::Sudo.
64              
65             See L for some hints on circumstances when this might happen.
66              
67             =head1 HOW IT WORKS
68              
69             Internally, your code will be de-parsed into its text form and then executed thus:
70              
71             system(
72             'sudo', '-p', '...', 'perl', '-T',
73             '-I...', ...
74             '-e',
75             "exit(do{ $your_code_here })"
76             ) >> 8;
77              
78             C might have to prompt for a password. If it does, then the prompt will
79             make it clear that this is Unix::Sudo asking for it.
80              
81             It's not just your code that is passed to the child process. There are also a
82             bunch of C<-I> arguments, so that it knows about any directories in the parent
83             process's C<@INC>, and it will also get copies of all the lexical variables
84             that are in scope in the calling code.
85              
86             Under the bonnet it uses L to turn your code-ref into text,
87             L's C to get variables, and L (and
88             C<$Data::Dumper::Deparse>) to turn those variables into text, all of which is
89             pre-pended to your code.
90              
91             =head1 CAVEATS
92              
93             Your code will always have C and C turned on, and be run with
94             taint-checking enabled. If you need to you can turn tainting off as shown in
95             the synopsis. Note that you can't just say 'no tainting', the C is
96             required, otherwise C, just like C, will be run at compile-time I
97             the calling code> and not in the child process where you need it.
98              
99             If your code needs to C any modules, or any subroutines that are imported,
100             you will need to say so inside the code-ref you pass. And again, remember that
101             you'll have to C any C statements.
102              
103             The variables that are passed through to your code are read-write, but any
104             changes you make are local to the child process so will not be communicated
105             back to the parent process.
106              
107             Any blessed references, tied variables, or objects that use C in
108             those variables may not behave as you expect. For example, a record that has
109             been read from a database won't have an active database connection; something
110             tied to a filehandle won't have an open filehandle; an object that uses
111             C to make reading its value have side-effects will not have those
112             side-effects respected in the parent process. In general, you should use this
113             to "promote" as little of your code as possible to run as root, and I
114             your code so that you can be as aware as possible of the preceding.
115              
116             =head1 DEBUGGING
117              
118             If your code isn't behaving as you expect or is dieing then I recommend that
119             you set UNIX_SUDO_SPILLGUTS=1 in your environment. This will cause Unix::Sudo
120             to C you about what it is about to execute before it does so.
121              
122             =head1 SECURITY CONCERNS
123              
124             This code will run potentially user-supplied code as root. I have done what I
125             can to avoid security hilarity, but if you allow someone to pass C
126             that's your problem.
127              
128             I have mitigated potential problems by:
129              
130             =over
131              
132             =item using the LIST form of C
133              
134             It shouldn't be possible to craft input that makes my code run your code as
135             root and then make my code run something else as root.
136              
137             It is of course still possible to make my code run your code as root and for
138             your code to then run other stuff as root.
139              
140             =item tainting is turned on
141              
142             That means that any input to your code from the outside world is internally
143             marked as being untrusted, and you are restricted in what you can do with it.
144             You can of course circumvent this by untainting, either in the usual regexy
145             ways or as noted above via C.
146              
147             =back
148              
149             I strongly recommend that you read and understand the source code and also read
150             L before using this code.
151              
152             =head1 STABILITY
153              
154             I make no promises about the stability of the interface, and it is subject to
155             change without notice. This is because I want to strongly encourage you to
156             read the documentation and the source code before installing new versions of
157             this code.
158              
159             I also therefore urge you that if you use this module in anything important
160             you should "pin" it to a particular version number in whatever you use for
161             managing your dependencies.
162              
163             =head1 BUGS/FEEDBACK
164              
165             Please report bugs at
166             L, including, if
167             possible, a test case.
168              
169             =head1 SOURCE CODE REPOSITORY
170              
171             L
172              
173             =head1 AUTHOR, COPYRIGHT and LICENCE
174              
175             Copyright 2019 David Cantrell EFE
176              
177             This software is free-as-in-speech software, and may be used, distributed, and
178             modified under the terms of either the GNU General Public Licence version 2 or
179             the Artistic Licence. It's up to you which one you use. The full text of the
180             licences can be found in the files GPL2.txt and ARTISTIC.txt, respectively.
181              
182             =head1 CONSPIRACY
183              
184             This module is also free-as-in-mason software.
185              
186             =cut
187              
188             sub sudo(&) {
189 0     0 1   my $context = peek_my(1);
190 0           my $code = '';
191              
192 0           my $deparse = B::Deparse->new();
193              
194             # declare all the variables first, in case one of them is a code-ref
195             # that refers to one of the others
196 0           foreach my $variable (keys %{$context}) {
  0            
197 0           $code .= "my $variable;\n";
198             }
199             # now give them their values
200 0           foreach my $variable (keys %{$context}) {
  0            
201 0           local $Data::Dumper::Deparse = 1;
202 0           local $Data::Dumper::Indent = 0;
203              
204 0           my $value = $context->{$variable};
205              
206 0 0         if(substr($variable, 0, 1) eq '%') {
    0          
    0          
207 0           $code .= "$variable = %{ (), do { my ".Dumper($value)."}};\n"
208             } elsif(substr($variable, 0, 1) eq '@') {
209 0           $code .= "$variable = \@{ (), do { my ".Dumper($value)."}};\n"
210             } elsif(substr($variable, 0, 1) eq '$') {
211 0 0 0       if(
212             ref($value) eq 'REF' &&
213 0           ref(${$value}) eq 'CODE'
214             ) {
215             $code .= "$variable = sub ".
216 0           $deparse->coderef2text(${$value}).
  0            
217             ";\n";
218             } else {
219 0           $code .= "$variable = \${ scalar do { my ".Dumper($value)."}};\n"
220             }
221             } else {
222 0           die("Sorry, Unix::Sudo can't cope with sigil '".
223             substr($variable, 0, 1).
224             "'\n");
225             }
226             }
227              
228 0           $code .= $deparse->coderef2text(shift);
229              
230 0 0         if($ENV{UNIX_SUDO_SPILLGUTS}) { warn $code }
  0            
231            
232             my $rv = system(
233             "sudo", "-p", "Unix::Sudo needs your password: ",
234             Probe::Perl->find_perl_interpreter(),
235 0           (map { "-I$_" } grep { -d } @INC),
  0            
  0            
236             "-T", "-e", "exit do { $code }"
237             ) >> 8;
238              
239 0 0         if($rv == 255) {
240 0           die(sprintf(
241             "Your code didn't compile when passed to Unix::Sudo::sudo at %s line %s\n",
242             (caller(1))[1, 2]
243             ));
244             } else {
245 0           return $rv
246             }
247             }
248              
249             1;