diff -r 2d52adc4adcc -r 4460fc10c6a3 ext/jail.c --- a/ext/jail.c Sat Feb 28 06:52:48 2009 +0000 +++ b/ext/jail.c Tue Mar 03 22:23:45 2009 +0000 @@ -1,5 +1,5 @@ /* - * jail.c - Ruby jparallel + * jail.c - Ruby jParallel * * vim: set nosta noet ts=4 sw=4: * @@ -39,13 +39,12 @@ * */ - #include "jail.h" -VALUE rbjail_mBSD; -VALUE rbjail_cBSDJail; -VALUE rbjail_cIPAddr; +/* -------------------------------------------------------------- + * Utility functions + * -------------------------------------------------------------- */ /* * Debug logging function @@ -83,7 +82,7 @@ if ( !rb_obj_is_kind_of(self, rbjail_cBSDJail) ) { rb_raise( rb_eTypeError, "wrong argument type %s (expected BSD::Jail)", - rb_class2name(CLASS_OF( self )) ); + rb_obj_classname( self )); } return DATA_PTR( self ); @@ -106,6 +105,62 @@ /* + * Attach to a running jail and chdir to the root. + */ +static int +rbjail_do_attach( int jid ) +{ + int attach_status = jail_attach(jid); + + if ( attach_status == -1 ) + rb_sys_fail( "jail_attach" ); + if ( chdir("/") == -1 ) rb_sys_fail( "chdir" ); + + return attach_status; +} + + +/* + * Attach to a running jail from within a block, forking first + * and returning the child pid. + */ +static VALUE +rbjail_attach_block( int jid ) +{ + int pid; + int status; + + if ( ! rb_block_given_p() ) return Qnil; + + rb_secure(2); + + fflush(stdout); + fflush(stderr); + + switch ( pid = fork() ) { + case 0: + rb_thread_atfork(); + rbjail_do_attach( jid ); + rb_protect( rb_yield, Qundef, &status ); + rb_exit( status ); + return Qnil; + + case -1: + rb_sys_fail( "fork(2)" ); + return Qnil; + + default: + return INT2FIX( pid ); + } +} + + + +/* -------------------------------------------------------------- + * Memory-management functions + * -------------------------------------------------------------- */ + +/* * Copy memory from the given 'xp' to a ruby managed object. */ static VALUE @@ -124,56 +179,6 @@ /* - * - */ -static VALUE -rbjail_get_ip( VALUE self ) -{ - struct xprison *xp = rbjail_get_jailptr( self ); - struct in_addr in; - char *ip; - - in.s_addr = ntohl( xp->pr_ip ); - ip = inet_ntoa(in); - - return rb_funcall( rbjail_cIPAddr, rb_intern("new"), 1, rb_str_new2(ip) ); -} - - -/* - * - */ -static VALUE -rbjail_get_jid( VALUE self ) -{ - struct xprison *xp = rbjail_get_jailptr( self ); - return INT2FIX( xp->pr_id ); -} - - -/* - * - */ -static VALUE -rbjail_get_host( VALUE self ) -{ - struct xprison *xp = rbjail_get_jailptr( self ); - return rb_str_new2( xp->pr_host ); -} - - -/* - * - */ -static VALUE -rbjail_get_path( VALUE self ) -{ - struct xprison *xp = rbjail_get_jailptr( self ); - return rb_str_new2( xp->pr_path ); -} - - -/* * GC Free function */ static void @@ -198,18 +203,30 @@ } +/* -------------------------------------------------------------- + * Class methods + * -------------------------------------------------------------- */ + /* - * Create a new jail object. - * Returns the +id+ of the newly created jail. + * call-seq: + * BSD::Jail.new( ip, path, host ) => jail_id + * + * Create a new BSD::Jail object from required +ip+, +path+, and +host+ + * arguments. You can optionally pass a +securelevel+ fourth argument. + * + * Returns the +id+ of the newly created jail. */ static VALUE -rbjail_jail( int argc, VALUE *argv, VALUE self ) { +rbjail_jail( int argc, VALUE *argv, VALUE self ) +{ struct jail j; struct in_addr in; - VALUE ip, path, host, securelevel; + VALUE ip, path, host, sec_level; int id; + int securelevel = -1; - rb_scan_args( argc, argv, "31", &ip, &path, &host, &securelevel ); + rb_scan_args( argc, argv, "31", &ip, &path, &host, &sec_level ); + if ( sec_level != Qnil ) securelevel = FIX2INT( sec_level ); if ( inet_aton( RSTRING_PTR( rb_obj_as_string(ip) ), &in ) == 0 ) rb_raise( rb_eArgError, "Could not make sense of ip number: %s", ip ); @@ -226,16 +243,62 @@ if ( id == -1 ) rb_sys_fail( "jail" ); if ( chdir("/") == -1 ) rb_sys_fail( "chdir" ); + if ( securelevel > -1 && securelevel < 4 ) { + debugMsg(( "Setting securelevel to: %d", securelevel )); + if ( sysctlbyname("kern.securelevel", NULL, 0, &securelevel, sizeof(securelevel)) ) + rb_sys_fail( "securelevel" ); + } + debugMsg(( "New jail created with id: %d\n", id )); return INT2FIX( id ); } /* - * Iterate over the currently instantiated jails, returning a jail - * object that matches the given string/ip/JID -- or nil if none do. - * Without an argument, return an array of all JIDs. - */ + * call-seq: + * BSD::Jail.find_by_jid( jid ) => BSD::Jail + * + * Iterate over the currently instantiated jails, returning a jail + * object that matches the given +jid+, or nil if no jail is found. + * + */ +static VALUE +rbjail_find_by_jid( VALUE self, VALUE jid ) +{ + VALUE args[0]; + + if ( TYPE(jid) != T_FIXNUM ) + rb_raise( rb_eTypeError, "invalid argument to find_by_jid(): %s", + RSTRING_PTR( rb_inspect(jid)) ); + + args[0] = jid; + return rbjail_find( 1, args, self ); +} + + +/* + * call-seq: + * BSD::Jail.search( id ) => BSD::Jail + * BSD::Jail.search( hostname ) => [ BSD::Jail, ... ] + * BSD::Jail.search( IPAddr.new('127.0.0.1') ) => [ BSD::Jail, ... ] + * BSD::Jail.search( Pathname.new('/tmp') ) => [ BSD::Jail, ... ] + * BSD::Jail.search => [ BSD::Jail, BSD::Jail, ... ] + * BSD::Jail.search { |obj| block } => obj + * + * Iterate over the currently instantiated jails, returning a jail + * object that matches the given argument. + * + * If the argument is an integer, it is assumed to be a JID. + * Otherwise, it is converted to a string, and compared against + * IP addresses, hostnames, and paths. JIDs are unique, so there is + * only one object that can match. Only the matching object (or nil) + * is returned in that event. IPs, hostnames, and paths can be + * shared between jails, so searching on those return an array populated + * with matched jail objects -- or if there are no valid matches, an empty array. + * + * Without an argument, return an array of all jails as objects. + * + */ static VALUE rbjail_find( int argc, VALUE *argv, VALUE self ) { @@ -262,8 +325,8 @@ jid = FIX2INT( arg ); break; - // find by IP/hostname - // + // find by IP/hostname/path + // case T_OBJECT: case T_DATA: case T_STRING: @@ -272,8 +335,8 @@ break; default: - rb_raise( rb_eTypeError, "invalid argument to find(): %s", - RSTRING_PTR(rb_inspect(arg)) ); + rb_raise( rb_eTypeError, "invalid argument: %s", + RSTRING_PTR( rb_inspect(arg)) ); } } @@ -302,10 +365,13 @@ if ( len < sizeof(*xp) || len % sizeof(*xp) || xp->pr_version != XPRISON_VERSION ) rb_fatal("Kernel and userland out of sync"); - // No arguments to find() -- return an array of all JIDs + // No arguments to find() -- yield each successive jail, + // and return an array of all jail objects. // if ( argc == 0 ) { for ( i = 0; i < len / sizeof(*xp); i++ ) { + rbjail = rbjail_alloc( self, xp ); + if ( rb_block_given_p() ) rb_yield( rbjail ); rb_ary_push( jails, rbjail_alloc( self, xp ) ); xp++; } @@ -315,21 +381,32 @@ } // Argument passed to find(): walk the jail list, comparing the arg - // with each current jail. Return first match. + // with each current jail. Return all matches. // for ( i = 0; i < len / sizeof(*xp); i++ ) { in.s_addr = ntohl(xp->pr_ip); if (( compare == 0 && xp->pr_id == jid ) || ( compare == 1 && (( strcmp( str, inet_ntoa(in) ) == 0 ) || - ( strcmp( str, xp->pr_host ) == 0 )) + ( strcmp( str, xp->pr_host ) == 0 ) || + ( strcmp( str, xp->pr_path ) == 0 )) )) { debugMsg(( "Located jail: %d", xp->pr_id )); - rbjail = rbjail_alloc( self, xp ); - free( sxp ); - return rbjail; + // If the user already knows the JID, there can only be + // one match. Return it immediately. + // + if ( compare == 0 ) { + free( sxp ); + return rbjail_alloc( self, xp ); + } + + // If searching for anything other than JID, the argument + // isn't unique. Put matching jails into an array. + // + rb_ary_push( jails, rbjail_alloc( self, xp ) ); + xp++; } else { @@ -338,102 +415,295 @@ } free( sxp ); - return Qnil; -} - -static void -rbjail_do_jail_attach( int jid ) -{ - if ( jail_attach(jid) == -1 ) - rb_sys_fail( "jail_attach" ); + if ( compare == 0 && rb_ary_shift( jails ) == Qnil ) + return Qnil; + + return jails; } -/* Mostly ripped off from Ruby's process.c */ +/* + * call-seq: + * BSD::Jail.attach( id ) => true + * BSD::Jail.attach( hostname ) => true + * + * Attach to a currently running jail instance directly. + * Operates under the same rules as BSD::Jail#search -- you may + * specify a jail by IP Address, hostname, or jid. + * + * Please note that attaching your process into a jail is a one way + * operation that requires root privileges. You must fork() if + * your process needs to continue in the host environment. + * + */ static VALUE -rbjail_attach_block( int jid ) +rbjail_class_attach( VALUE self, VALUE arg ) { - int pid; - - rb_secure(2); - - fflush(stdout); - fflush(stderr); + int jid = 0; + VALUE jails; + VALUE find_args [0]; - switch ( pid = fork() ) { - case 0: - rb_thread_atfork(); - if ( rb_block_given_p() ) { - int status; + switch ( TYPE(arg) ) { + + // The user knows the JID already, attach directly. + // + case T_FIXNUM: + + jid = FIX2INT( arg ); + rbjail_do_attach( jid ); + break; - rbjail_do_jail_attach( jid ); - rb_protect( rb_yield, Qundef, &status ); - ruby_stop( status ); - } - return Qnil; + // Find the JID to attach to. First match wins. + // + case T_OBJECT: + case T_DATA: + case T_STRING: - case -1: - rb_sys_fail( "fork(2)" ); - return Qnil; + find_args[0] = arg; + jails = rbjail_find( 1, find_args, self ); + jid = FIX2INT( rbjail_get_jid( rb_ary_shift(jails) )); + rbjail_do_attach( jid ); + break; default: - return INT2FIX( pid ); + rb_raise( rb_eTypeError, "invalid argument to attach(): %s", + RSTRING_PTR( rb_inspect(arg)) ); } + + return Qtrue; } + +/* -------------------------------------------------------------- + * Instance methods + * -------------------------------------------------------------- */ + +/* + * call-seq: + * BSD::Jail <=> BSD::Jail + * + * Interface for Comparable. + * + */ static VALUE -rbjail_attach( int argc, VALUE *argv, VALUE self ) +rbjail_compare( VALUE self, VALUE other ) { - VALUE jidnum, rval; - int jid; + debugMsg(("self id: %s, other id: %s", + RSTRING_PTR(rb_inspect( self )), + RSTRING_PTR(rb_inspect( other )))); - rb_scan_args( argc, argv, "1", &jidnum ); - jid = NUM2INT( jidnum ); + return rb_funcall( + rbjail_get_jid( self ), + rb_intern("<=>"), + 1, + rbjail_get_jid( other ) + ); +} + - if ( rb_block_given_p() ) { - rval = rbjail_attach_block( jid ); - } +/* + * Fetch the configured IP address for the jail. + * Returns an IPAddr object. + * + */ +static VALUE +rbjail_get_ip( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + struct in_addr in; + VALUE args [0]; - else { - rbjail_do_jail_attach( jid ); - rval = Qtrue; - } + in.s_addr = ntohl( xp->pr_ip ); + args[0] = rb_str_new2( inet_ntoa(in) ); + + return rb_class_new_instance( 1, args, rbjail_cIPAddr ); +} + - return rval; +/* + * Fetch the assigned JID for the jail. + * + */ +static VALUE +rbjail_get_jid( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + return INT2FIX( xp->pr_id ); } /* - * Ruby initializer. + * Fetch the configured hostname for the jail as a string. + * + */ +static VALUE +rbjail_get_host( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + return rb_str_new2( xp->pr_host ); +} + + +/* + * Fetch the configured path for the jail. + * Returns a Pathname object. + * + */ +static VALUE +rbjail_get_path( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + VALUE args[0]; + + args[0] = rb_str_new2( xp->pr_path ); + + return rb_class_new_instance( 1, args, rbjail_cPathname ); +} + + +/* + * Return a human readable version of the object. + */ +static VALUE +rbjail_inspect( VALUE self ) +{ + char inspect_str[BUFSIZ]; + + sprintf( inspect_str, "#<%s:0x%07x jid:%d (%s)>", + rb_obj_classname( self ), + NUM2UINT(rb_obj_id( self )) * 2, + FIX2INT(rbjail_get_jid( self )), + RSTRING_PTR(rbjail_get_host( self )) + ); + + return rb_str_new2( inspect_str ); +} + + +/* + * call-seq: + * jail.attach + * jail.attach do ... end => child pid + * + * Attach to the jail. + * + * Please note that attaching your process into a jail is a one way + * operation that requires root privileges. You must fork() if + * your process needs to continue in the host environment. + * + * In the block form, a fork() is performed automatically. + * + */ +static VALUE +rbjail_instance_attach( VALUE self ) +{ + int jid = FIX2INT( rbjail_get_jid( self ) ); + + if ( rb_block_given_p() ) { + return rbjail_attach_block( jid ); + } + + else { + rbjail_do_attach( jid ); + } + + return Qtrue; +} + + +/* -------------------------------------------------------------- + * Initalizer + * -------------------------------------------------------------- */ + +/* + * BSD::Jail + * + * Ruby bindings for the FreeBSD jail(2) subsystem. + * + * Example usage: + * + * require 'bsd/jail' + * + * # create a new jail + * jid = BSD::Jail.create( '127.0.0.1', '/tmp', 'testjail' ) + * + * # find existing jail(s) + * jail = BSD::Jail.find_by_jid( jid ) + * jails = BSD::Jail.search( hostname ) + * jails = BSD::Jail.search( IPAddr.new('127.0.0.1') ) + * jails = BSD::Jail.search( Pathname.new('/tmp') ) + * + * # attach to jails + * BSD::Jail.attach( jid ) + * jail.attach do + * ... do something fancy! + * end + * + * BSD::Jail includes behaviors from Enumerable and Comparable, + * so you can do things such as the following: + * + * # Are these two instantiated jails the same jid? + * BSD::Jail.search( '/tmp' ) == BSD::Jail.search( 'testjail' ) + * + * # Alternative interface for finding a jail by jid + * BSD::Jail.find { |j| j.jid == 3 } + * + * # Return all jails as an array + * jails = BSD::Jail.find_all + * + * # What jails have IPs within the 192.168.16.0/24 class C netblock? + * nb = IPAddr.new( '192.168.16.0/24' ); + * jails = BSD::Jail.find? { |j| nb.include?(j.ip) } + * */ void Init_jail( void ) { + // namespacing + // rbjail_mBSD = rb_define_module( "BSD" ); rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail", rb_cObject ); - rb_require("ipaddr"); - rbjail_cIPAddr = rb_const_get( rb_cObject, rb_intern("IPAddr") ); - + // struct wrapping function rb_define_alloc_func( rbjail_cBSDJail, rbjail_s_alloc ); // Make the 'new' method private. rb_funcall( rbjail_cBSDJail, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("new")) ); - // main utility functions + // class methods + // + rb_define_singleton_method( rbjail_cBSDJail, "attach", rbjail_class_attach, 1 ); + rb_define_singleton_method( rbjail_cBSDJail, "create", rbjail_jail, -1 ); + rb_define_singleton_method( rbjail_cBSDJail, "search", rbjail_find, -1 ); + rb_define_singleton_method( rbjail_cBSDJail, "find_by_jid", rbjail_find_by_jid, 1 ); + + // instance methods // - rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail, -1 ); - rb_define_singleton_method( rbjail_cBSDJail, "find", rbjail_find, -1 ); - rb_define_method( rbjail_cBSDJail, "attach", rbjail_attach, -1 ); - // rb_define_alias( rbjail_cBSDJail, "list", "find" ); - - // accessor functions + rb_define_method( rbjail_cBSDJail, "attach", rbjail_instance_attach, 0 ); + rb_define_method( rbjail_cBSDJail, "host", rbjail_get_host, 0 ); + rb_define_alias( rbjail_cBSDJail, "hostname", "host" ); + rb_define_method( rbjail_cBSDJail, "inspect", rbjail_inspect, 0 ); + rb_define_method( rbjail_cBSDJail, "ip", rbjail_get_ip, 0 ); + rb_define_method( rbjail_cBSDJail, "jid", rbjail_get_jid, 0 ); + rb_define_method( rbjail_cBSDJail, "path", rbjail_get_path, 0 ); + + // Additional (base) modules for accessor wrapping // - rb_define_method( rbjail_cBSDJail, "jid", rbjail_get_jid, 0 ); - rb_define_method( rbjail_cBSDJail, "ip", rbjail_get_ip, 0 ); - rb_define_method( rbjail_cBSDJail, "host", rbjail_get_host, 0 ); - rb_define_method( rbjail_cBSDJail, "path", rbjail_get_path, 0 ); + rb_require( "ipaddr" ); + rb_require( "pathname" ); + rbjail_cIPAddr = rb_const_get( rb_cObject, rb_intern("IPAddr") ); + rbjail_cPathname = rb_const_get( rb_cObject, rb_intern("Pathname") ); + + // Enumerable! + // + rb_define_singleton_method( rbjail_cBSDJail, "each", rbjail_find, -1 ); + rb_extend_object( rbjail_cBSDJail, rb_const_get( rb_cObject, rb_intern("Enumerable") )); + + // Comparable! + // + rb_define_method( rbjail_cBSDJail, "<=>", rbjail_compare, 1 ); + rb_include_module( rbjail_cBSDJail, rb_const_get( rb_cObject, rb_intern("Comparable") )); }