package BSD::Jail::Object; use strict; use warnings; use vars qw/ @ISA @EXPORT_OK /; use Exporter; our $VERSION = '0.02'; @ISA = qw/ Exporter /; @EXPORT_OK = qw/ jids /; use Inline C => 'DATA', NAME => 'BSD::Jail::Object', VERSION => '0.02'; sub new { my ( $class, $opts ) = @_; my $self = {}; bless $self, $class; return $self unless $opts; if ( ref $opts eq 'HASH' ) { # create a new jail if ( $< ) { $@ = "jail() requires root"; return; } unless ( $opts->{'path'} && $opts->{'hostname'} && $opts->{'ip'} ) { $@ = "Missing arguments to create() - need 'path', 'hostname', and 'ip'"; return; } my $jid = _create( $opts->{'path'}, $opts->{'hostname'}, $opts->{'ip'} ) or return; $self->{'_data'} = [ $jid, $opts->{'ip'}, $opts->{'hostname'}, $opts->{'path'} ]; return $self; } else { # this object should be linked to an existing jail return $self->_init( $opts ); } } sub _init { my $self = shift; my $key = shift; return unless $key; my ( @data, $type ); if ( $key =~ /^\d+$/ ) { $type = 'jid'; @data = _find_jail( 0, $key ); } elsif ( $key =~ /^\d+\.\d+\.\d+\.\d+$/ ) { $type = 'ip'; @data = _find_jail( 1, $key ); } else { $type = 'hostname'; @data = _find_jail( 2, $key ); } unless ( scalar @data ) { $@ = "No such jail $type: $key"; return; } $self->{'_data'} = \@data; return $self; } sub jid { shift()->{'_data'}->[0] } sub ip { shift()->{'_data'}->[1] } sub hostname { shift()->{'_data'}->[2] } sub path { shift()->{'_data'}->[3] } sub attach { my $self = shift; return unless $self->jid; if ( $< ) { $@ = "jail_attach() requires root"; return; } return _attach( $self->jid ); } sub jids { return if ref $_[0]; # shouldn't be used as an object method my %opts = @_; my @jids = _find_jids(); return @jids unless $opts{'instantiate'}; map { $_ = __PACKAGE__->new( $_ ) } @jids; return @jids; } 1; __DATA__ =pod =head1 DESCRIPTION This is an object oriented wrapper around the FreeBSD jail subsystem. A 5.x or higher FreeBSD system is required. =head1 SYNOPSIS Here is an exact replica of the 'jls' utility in just a few lines of perl: use BSD::Jail::Object 'jids'; print " JID IP Address Hostname Path\n"; printf "%6d %-15.15s %-29.29s %.74s\n", $_->jid, $_->ip, $_->hostname, $_->path foreach jids( instantiate => 1 ); And here's 'jexec' (actually, a jexec that lets you optionally select by something other than jid): my $j = BSD::Jail::Object->new( $ARGV[0] ) or die $@; $j->attach && chdir('/') && exec $ARGV[1] or exit; =head1 EXAMPLES =over 4 =item B $options = { path => '/tmp', ip => '127.0.0.1', hostname => 'example.com' }; $j = BSD::Jail::Object->new( $options ) or die $@; =item B $j = BSD::Jail::Object->new( 'example.com' ); $j->attach; =item B foreach $j ( jids(instantiate => 1) ) { if ( fork ) { $j->attach; # # do something exciting # exit; } } =item B (See the B section above) =back =head1 OBJECT METHODS =head2 new() Instantiate a new BSD::Jail::Object object, either by associating ourselves with an already running jail, or by creating a new one from scratch. To associate with an already active jail, I accepts a jid, hostname, or ip address. Errors are placed into $@. # existing jail, find by jid $j = BSD::Jail::Object->new( 23 ) or die $@; # existing jail, find by hostname $j = BSD::Jail::Object->new( 'example.com' ) or die $@; # existing jail, find by ip address $j = BSD::Jail::Object->new( '127.0.0.1' ) or die $@; Note that if you're selecting a jail by hostname or IP, those aren't always unique values. Two jails could be running with the same hostname or IP address - this module will always select the highest numbered jid in that case. If you need to be sure you're in the 'right' jail when there are duplicates, select by JID. Create a new jail by passing a hash reference. Required keys are 'hostname', 'ip', and 'path'. See the I man page for specifics on these keys. # create a new jail under /tmp $j = BSD::Jail::Object->new({ hostname => 'example.com', ip => '127.0.0.1', path => '/tmp' }) or die $@; =head2 jid() Get the current jail identifier. JIDs are assigned sequentially from the kernel. =head2 hostname() Get the current jail hostname. =head2 path() Get the root path the jail was bound to. =head2 attach() Imprison ourselves within a jail. Note that this generally requires root access, and is a one way operation. Once the script process is imprisioned, there is no way to perform a jailbreak! You'd need to I if you intended to attach to more than one jail. See I. =head1 EXPORTABLE METHODS =head2 jids() Returns an array of active JIDs. Can also return them as pre-instantiated objects by passing 'instantiate => 1' as an argument. my @jail_jids = jids(); my @jail_objects = jids( instantiate => 1 ); Only exported upon request. =head1 ACKNOWLEDGEMENTS Most of the jail specific C code was based on work by Mike Barcroft and Poul-Henning Kamp for the FreeBSD Project. =head1 AUTHOR Mahlon E. Smith I for Spime Solutions Group I<(www.spime.net)> =cut __C__ #include #include #include #include #include #include #include #include size_t sysctl_len() { size_t len; if ( sysctlbyname( "security.jail.list", NULL, &len, NULL, 0 ) == -1 ) return 0; return len; } // get jail structure from kernel struct xprison *get_xp() { struct xprison *sxp, *xp; size_t len; len = sysctl_len(); if ( len <= 0 ) return NULL; sxp = xp = malloc(len); if ( sxp == NULL ) return NULL; // populate the xprison list if ( sysctlbyname( "security.jail.list", xp, &len, NULL, 0 ) == -1 ) { if (errno == ENOMEM) { free( sxp ); return NULL; } return NULL; } // check if kernel and userland is in sync if ( len < sizeof(*xp) || len % sizeof(*xp) || xp->pr_version != XPRISON_VERSION ) { warn("%s", "Kernel out of sync with userland"); return NULL; } free( sxp ); return xp; } // fetch a specific jail's information void _find_jail( int compare, char *string ) { struct xprison *xp; struct in_addr in; size_t i, len; Inline_Stack_Vars; Inline_Stack_Reset; xp = get_xp(); len = sysctl_len(); /* compare == 0 jid compare == 1 ip address compare == 2 hostname */ for (i = 0; i < len / sizeof(*xp); i++) { in.s_addr = ntohl(xp->pr_ip); if ( ( compare == 0 && xp->pr_id == atoi(string) ) || ( compare == 1 && strcmp( string, inet_ntoa(in) ) == 0 ) || ( compare == 2 && strcmp( string, xp->pr_host ) == 0 ) ) { Inline_Stack_Push( sv_2mortal( newSViv( xp->pr_id ) )); Inline_Stack_Push( sv_2mortal( newSVpvf( inet_ntoa(in) ) )); Inline_Stack_Push( sv_2mortal( newSVpvf( xp->pr_host ) )); Inline_Stack_Push( sv_2mortal( newSVpvf( xp->pr_path ) )); break; } else { xp++; } } Inline_Stack_Done; } // return an array of all current jail ids void _find_jids() { struct xprison *xp; size_t i, len; Inline_Stack_Vars; Inline_Stack_Reset; xp = get_xp(); len = sysctl_len(); for (i = 0; i < len / sizeof(*xp); i++) { Inline_Stack_Push( sv_2mortal( newSViv( xp->pr_id ) )); xp++; } Inline_Stack_Done; } // attach to a jail int _attach( int jid ) { return ( jail_attach(jid) == -1 ? 0 : 1 ); } // create a new jail int _create( char *path, char *hostname, char *ipaddr ) { struct in_addr ip; struct jail j; int jid; if ( inet_aton( ipaddr, &ip ) == 0 ) return 0; j.path = path; j.hostname = hostname; j.ip_number = ntohl( ip.s_addr ); j.version = 0; if ( (jid = jail( &j )) == -1 ) return 0; return jid; }