bsd-jail-object/Object.pm
branchperl-modules
changeset 0 315eb12b224b
--- /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<Create a new jail>
+
+ $options = {
+     path     => '/tmp',
+     ip       => '127.0.0.1',
+     hostname => 'example.com'
+ };
+
+ $j = BSD::Jail::Object->new( $options ) or die $@;
+
+=item B<Attach to an existing jail>
+
+ $j = BSD::Jail::Object->new( 'example.com' );
+ $j->attach;
+
+=item B<Do something in all jails>
+
+ foreach $j ( jids(instantiate => 1) ) {
+
+     if ( fork ) {
+         $j->attach;
+
+         #
+         # do something exciting
+         #
+
+         exit;
+     }
+ }
+
+=item B<Get information on a jail>
+
+(See the B<SYNOPSIS> 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<new()> 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<jail(8)> 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<fork()> if you intended to attach to more than one jail.  See
+I<EXAMPLES>.
+
+=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 <mike@freebsd.org> and Poul-Henning Kamp <phk@freebsd.org>
+for the FreeBSD Project.
+
+=head1 AUTHOR
+
+Mahlon E. Smith I<mahlon@martini.nu> for Spime Solutions Group
+I<(www.spime.net)>
+
+=cut
+
+__C__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/param.h>
+#include <sys/jail.h>
+
+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;
+}
+