|
1 |
|
2 package BSD::Jail::Object; |
|
3 use strict; |
|
4 use warnings; |
|
5 use vars qw/ @ISA @EXPORT_OK /; |
|
6 use Exporter; |
|
7 |
|
8 our $VERSION = '0.02'; |
|
9 @ISA = qw/ Exporter /; |
|
10 @EXPORT_OK = qw/ jids /; |
|
11 |
|
12 use Inline C => 'DATA', |
|
13 NAME => 'BSD::Jail::Object', |
|
14 VERSION => '0.02'; |
|
15 |
|
16 sub new |
|
17 { |
|
18 my ( $class, $opts ) = @_; |
|
19 |
|
20 my $self = {}; |
|
21 bless $self, $class; |
|
22 return $self unless $opts; |
|
23 |
|
24 if ( ref $opts eq 'HASH' ) { |
|
25 |
|
26 # create a new jail |
|
27 |
|
28 if ( $< ) { |
|
29 $@ = "jail() requires root"; |
|
30 return; |
|
31 } |
|
32 |
|
33 unless ( $opts->{'path'} && |
|
34 $opts->{'hostname'} && |
|
35 $opts->{'ip'} ) { |
|
36 $@ = "Missing arguments to create() - need 'path', 'hostname', and 'ip'"; |
|
37 return; |
|
38 } |
|
39 |
|
40 my $jid = _create( $opts->{'path'}, $opts->{'hostname'}, $opts->{'ip'} ) |
|
41 or return; |
|
42 |
|
43 $self->{'_data'} = [ |
|
44 $jid, $opts->{'ip'}, $opts->{'hostname'}, $opts->{'path'} |
|
45 ]; |
|
46 |
|
47 return $self; |
|
48 } |
|
49 else { |
|
50 |
|
51 # this object should be linked to an existing jail |
|
52 return $self->_init( $opts ); |
|
53 |
|
54 } |
|
55 } |
|
56 |
|
57 sub _init |
|
58 { |
|
59 my $self = shift; |
|
60 my $key = shift; |
|
61 |
|
62 return unless $key; |
|
63 |
|
64 my ( @data, $type ); |
|
65 if ( $key =~ /^\d+$/ ) { |
|
66 $type = 'jid'; |
|
67 @data = _find_jail( 0, $key ); |
|
68 } |
|
69 elsif ( $key =~ /^\d+\.\d+\.\d+\.\d+$/ ) { |
|
70 $type = 'ip'; |
|
71 @data = _find_jail( 1, $key ); |
|
72 } |
|
73 else { |
|
74 $type = 'hostname'; |
|
75 @data = _find_jail( 2, $key ); |
|
76 } |
|
77 |
|
78 unless ( scalar @data ) { |
|
79 $@ = "No such jail $type: $key"; |
|
80 return; |
|
81 } |
|
82 |
|
83 $self->{'_data'} = \@data; |
|
84 return $self; |
|
85 } |
|
86 |
|
87 sub jid { shift()->{'_data'}->[0] } |
|
88 sub ip { shift()->{'_data'}->[1] } |
|
89 sub hostname { shift()->{'_data'}->[2] } |
|
90 sub path { shift()->{'_data'}->[3] } |
|
91 |
|
92 sub attach |
|
93 { |
|
94 my $self = shift; |
|
95 return unless $self->jid; |
|
96 |
|
97 if ( $< ) { |
|
98 $@ = "jail_attach() requires root"; |
|
99 return; |
|
100 } |
|
101 |
|
102 return _attach( $self->jid ); |
|
103 } |
|
104 |
|
105 sub jids |
|
106 { |
|
107 return if ref $_[0]; # shouldn't be used as an object method |
|
108 |
|
109 my %opts = @_; |
|
110 |
|
111 my @jids = _find_jids(); |
|
112 return @jids unless $opts{'instantiate'}; |
|
113 |
|
114 map { $_ = __PACKAGE__->new( $_ ) } @jids; |
|
115 return @jids; |
|
116 } |
|
117 |
|
118 1; |
|
119 |
|
120 __DATA__ |
|
121 |
|
122 =pod |
|
123 |
|
124 =head1 DESCRIPTION |
|
125 |
|
126 This is an object oriented wrapper around the FreeBSD jail subsystem. |
|
127 |
|
128 A 5.x or higher FreeBSD system is required. |
|
129 |
|
130 =head1 SYNOPSIS |
|
131 |
|
132 Here is an exact replica of the 'jls' utility in just a few lines of perl: |
|
133 |
|
134 use BSD::Jail::Object 'jids'; |
|
135 |
|
136 print " JID IP Address Hostname Path\n"; |
|
137 printf "%6d %-15.15s %-29.29s %.74s\n", |
|
138 $_->jid, $_->ip, $_->hostname, $_->path foreach jids( instantiate => 1 ); |
|
139 |
|
140 And here's 'jexec' (actually, a jexec that lets you optionally select by |
|
141 something other than jid): |
|
142 |
|
143 my $j = BSD::Jail::Object->new( $ARGV[0] ) or die $@; |
|
144 $j->attach && chdir('/') && exec $ARGV[1] or exit; |
|
145 |
|
146 =head1 EXAMPLES |
|
147 |
|
148 =over 4 |
|
149 |
|
150 =item B<Create a new jail> |
|
151 |
|
152 $options = { |
|
153 path => '/tmp', |
|
154 ip => '127.0.0.1', |
|
155 hostname => 'example.com' |
|
156 }; |
|
157 |
|
158 $j = BSD::Jail::Object->new( $options ) or die $@; |
|
159 |
|
160 =item B<Attach to an existing jail> |
|
161 |
|
162 $j = BSD::Jail::Object->new( 'example.com' ); |
|
163 $j->attach; |
|
164 |
|
165 =item B<Do something in all jails> |
|
166 |
|
167 foreach $j ( jids(instantiate => 1) ) { |
|
168 |
|
169 if ( fork ) { |
|
170 $j->attach; |
|
171 |
|
172 # |
|
173 # do something exciting |
|
174 # |
|
175 |
|
176 exit; |
|
177 } |
|
178 } |
|
179 |
|
180 =item B<Get information on a jail> |
|
181 |
|
182 (See the B<SYNOPSIS> section above) |
|
183 |
|
184 =back |
|
185 |
|
186 =head1 OBJECT METHODS |
|
187 |
|
188 =head2 new() |
|
189 |
|
190 Instantiate a new BSD::Jail::Object object, either by associating |
|
191 ourselves with an already running jail, or by creating a new one from |
|
192 scratch. |
|
193 |
|
194 To associate with an already active jail, I<new()> accepts a jid, |
|
195 hostname, or ip address. Errors are placed into $@. |
|
196 |
|
197 # existing jail, find by jid |
|
198 $j = BSD::Jail::Object->new( 23 ) or die $@; |
|
199 |
|
200 # existing jail, find by hostname |
|
201 $j = BSD::Jail::Object->new( 'example.com' ) or die $@; |
|
202 |
|
203 # existing jail, find by ip address |
|
204 $j = BSD::Jail::Object->new( '127.0.0.1' ) or die $@; |
|
205 |
|
206 Note that if you're selecting a jail by hostname or IP, those aren't |
|
207 always unique values. Two jails could be running with the same hostname |
|
208 or IP address - this module will always select the highest numbered jid |
|
209 in that case. If you need to be sure you're in the 'right' jail when |
|
210 there are duplicates, select by JID. |
|
211 |
|
212 Create a new jail by passing a hash reference. Required keys are |
|
213 'hostname', 'ip', and 'path'. See the I<jail(8)> man page for specifics |
|
214 on these keys. |
|
215 |
|
216 # create a new jail under /tmp |
|
217 $j = BSD::Jail::Object->new({ |
|
218 hostname => 'example.com', |
|
219 ip => '127.0.0.1', |
|
220 path => '/tmp' |
|
221 }) or die $@; |
|
222 |
|
223 =head2 jid() |
|
224 |
|
225 Get the current jail identifier. JIDs are assigned sequentially from |
|
226 the kernel. |
|
227 |
|
228 =head2 hostname() |
|
229 |
|
230 Get the current jail hostname. |
|
231 |
|
232 =head2 path() |
|
233 |
|
234 Get the root path the jail was bound to. |
|
235 |
|
236 =head2 attach() |
|
237 |
|
238 Imprison ourselves within a jail. Note that this generally requires |
|
239 root access, and is a one way operation. Once the script process |
|
240 is imprisioned, there is no way to perform a jailbreak! You'd need |
|
241 to I<fork()> if you intended to attach to more than one jail. See |
|
242 I<EXAMPLES>. |
|
243 |
|
244 =head1 EXPORTABLE METHODS |
|
245 |
|
246 =head2 jids() |
|
247 |
|
248 Returns an array of active JIDs. Can also return them as |
|
249 pre-instantiated objects by passing 'instantiate => 1' as an argument. |
|
250 |
|
251 my @jail_jids = jids(); |
|
252 my @jail_objects = jids( instantiate => 1 ); |
|
253 |
|
254 Only exported upon request. |
|
255 |
|
256 =head1 ACKNOWLEDGEMENTS |
|
257 |
|
258 Most of the jail specific C code was based on work |
|
259 by Mike Barcroft <mike@freebsd.org> and Poul-Henning Kamp <phk@freebsd.org> |
|
260 for the FreeBSD Project. |
|
261 |
|
262 =head1 AUTHOR |
|
263 |
|
264 Mahlon E. Smith I<mahlon@martini.nu> for Spime Solutions Group |
|
265 I<(www.spime.net)> |
|
266 |
|
267 =cut |
|
268 |
|
269 __C__ |
|
270 |
|
271 #include <stdio.h> |
|
272 #include <stdlib.h> |
|
273 #include <unistd.h> |
|
274 |
|
275 #include <arpa/inet.h> |
|
276 #include <errno.h> |
|
277 #include <limits.h> |
|
278 |
|
279 #include <sys/param.h> |
|
280 #include <sys/jail.h> |
|
281 |
|
282 size_t |
|
283 sysctl_len() |
|
284 { |
|
285 size_t len; |
|
286 if ( sysctlbyname( "security.jail.list", NULL, &len, NULL, 0 ) == -1 ) return 0; |
|
287 |
|
288 return len; |
|
289 } |
|
290 |
|
291 // get jail structure from kernel |
|
292 struct xprison |
|
293 *get_xp() |
|
294 { |
|
295 struct xprison *sxp, *xp; |
|
296 size_t len; |
|
297 |
|
298 len = sysctl_len(); |
|
299 if ( len <= 0 ) return NULL; |
|
300 |
|
301 sxp = xp = malloc(len); |
|
302 if ( sxp == NULL ) return NULL; |
|
303 |
|
304 // populate the xprison list |
|
305 if ( sysctlbyname( "security.jail.list", xp, &len, NULL, 0 ) == -1 ) { |
|
306 if (errno == ENOMEM) { |
|
307 free( sxp ); |
|
308 return NULL; |
|
309 } |
|
310 return NULL; |
|
311 } |
|
312 |
|
313 // check if kernel and userland is in sync |
|
314 if ( len < sizeof(*xp) || len % sizeof(*xp) || |
|
315 xp->pr_version != XPRISON_VERSION ) { |
|
316 warn("%s", "Kernel out of sync with userland"); |
|
317 return NULL; |
|
318 } |
|
319 |
|
320 free( sxp ); |
|
321 return xp; |
|
322 } |
|
323 |
|
324 // fetch a specific jail's information |
|
325 void |
|
326 _find_jail( int compare, char *string ) |
|
327 { |
|
328 struct xprison *xp; |
|
329 struct in_addr in; |
|
330 size_t i, len; |
|
331 Inline_Stack_Vars; |
|
332 |
|
333 Inline_Stack_Reset; |
|
334 xp = get_xp(); |
|
335 len = sysctl_len(); |
|
336 |
|
337 /* |
|
338 compare == 0 jid |
|
339 compare == 1 ip address |
|
340 compare == 2 hostname |
|
341 */ |
|
342 |
|
343 for (i = 0; i < len / sizeof(*xp); i++) { |
|
344 in.s_addr = ntohl(xp->pr_ip); |
|
345 if ( |
|
346 ( compare == 0 && xp->pr_id == atoi(string) ) |
|
347 || |
|
348 ( compare == 1 && strcmp( string, inet_ntoa(in) ) == 0 ) |
|
349 || |
|
350 ( compare == 2 && strcmp( string, xp->pr_host ) == 0 ) |
|
351 ) { |
|
352 Inline_Stack_Push( sv_2mortal( newSViv( xp->pr_id ) )); |
|
353 Inline_Stack_Push( sv_2mortal( newSVpvf( inet_ntoa(in) ) )); |
|
354 Inline_Stack_Push( sv_2mortal( newSVpvf( xp->pr_host ) )); |
|
355 Inline_Stack_Push( sv_2mortal( newSVpvf( xp->pr_path ) )); |
|
356 break; |
|
357 } |
|
358 else { |
|
359 xp++; |
|
360 } |
|
361 } |
|
362 |
|
363 Inline_Stack_Done; |
|
364 } |
|
365 |
|
366 // return an array of all current jail ids |
|
367 void |
|
368 _find_jids() |
|
369 { |
|
370 struct xprison *xp; |
|
371 size_t i, len; |
|
372 Inline_Stack_Vars; |
|
373 |
|
374 Inline_Stack_Reset; |
|
375 xp = get_xp(); |
|
376 len = sysctl_len(); |
|
377 |
|
378 for (i = 0; i < len / sizeof(*xp); i++) { |
|
379 Inline_Stack_Push( sv_2mortal( newSViv( xp->pr_id ) )); |
|
380 xp++; |
|
381 } |
|
382 |
|
383 Inline_Stack_Done; |
|
384 } |
|
385 |
|
386 // attach to a jail |
|
387 int |
|
388 _attach( int jid ) |
|
389 { |
|
390 return ( jail_attach(jid) == -1 ? 0 : 1 ); |
|
391 } |
|
392 |
|
393 // create a new jail |
|
394 int |
|
395 _create( char *path, char *hostname, char *ipaddr ) |
|
396 { |
|
397 struct in_addr ip; |
|
398 struct jail j; |
|
399 int jid; |
|
400 |
|
401 if ( inet_aton( ipaddr, &ip ) == 0 ) return 0; |
|
402 |
|
403 j.path = path; |
|
404 j.hostname = hostname; |
|
405 j.ip_number = ntohl( ip.s_addr ); |
|
406 j.version = 0; |
|
407 |
|
408 if ( (jid = jail( &j )) == -1 ) return 0; |
|
409 |
|
410 return jid; |
|
411 } |
|
412 |