diff -r 000000000000 -r 315eb12b224b bsd-jail-object/Object.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bsd-jail-object/Object.pm Fri Sep 05 05:15:48 2008 +0000 @@ -0,0 +1,412 @@ + +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; +} +