diff -r 41a1542b3b10 -r 2d52adc4adcc ext/jail.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/jail.c Sat Feb 28 06:52:48 2009 +0000 @@ -0,0 +1,439 @@ +/* + * jail.c - Ruby jparallel + * + * vim: set nosta noet ts=4 sw=4: + * + * $Id$ + * + * Authors: + * * Michael Granger + * * Mahlon E. Smith + * + * Copyright (c) 2008, Michael Granger and Mahlon E. Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the author/s, nor the names of the project's + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + +#include "jail.h" + +VALUE rbjail_mBSD; +VALUE rbjail_cBSDJail; +VALUE rbjail_cIPAddr; + + +/* + * Debug logging function + */ +void +#ifdef HAVE_STDARG_PROTOTYPES +rbjail_debug(const char *fmt, ...) +#else +rbjail_debug( const char *fmt, va_dcl ) +#endif +{ + char buf[BUFSIZ], buf2[BUFSIZ]; + va_list args; + + if ( !RTEST(ruby_debug) ) return; + + snprintf( buf, BUFSIZ, "Debug>>> %s", fmt ); + + va_init_list( args, fmt ); + vsnprintf( buf2, BUFSIZ, buf, args ); + fputs( buf2, stderr ); + fputs( "\n", stderr ); + fflush( stderr ); + va_end( args ); +} + + +/* + * Object validity checker. Returns the data pointer. + */ +static struct xprison * +rbjail_check_jail( VALUE self ) { + debugMsg(( "Checking a BSD::Jail object (%d).", self )); + Check_Type( self, T_DATA ); + + 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 )) ); + } + + return DATA_PTR( self ); +} + + +/* + * Fetch the data pointer and check it for sanity. + */ +static struct xprison * +rbjail_get_jailptr( VALUE self ) { + struct xprison *ptr = rbjail_check_jail( self ); + + debugMsg(( "Fetching a Jail (%p).", ptr )); + if ( !ptr ) + rb_raise( rb_eRuntimeError, "uninitialized Jail" ); + + return ptr; +} + + +/* + * Copy memory from the given 'xp' to a ruby managed object. + */ +static VALUE +rbjail_alloc( VALUE class, struct xprison *xp ) +{ + struct xprison *rbjail_xp = ALLOC( struct xprison ); + VALUE rbjail = rb_funcall( class, rb_intern("allocate"), 0 ); + + // replace the null pointer obj with an xprison ptr. + // + memcpy( rbjail_xp, xp, sizeof( struct xprison ) ); + DATA_PTR( rbjail ) = rbjail_xp; + + return rbjail; +} + + +/* + * + */ +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 +rbjail_gc_free( struct xprison *ptr ) { + if ( ptr ) { + xfree( ptr ); + } + + else { + debugMsg(( "Not freeing an uninitialized jail" )); + } +} + + +/* + * Allocate a new ruby object with a NULL pointer. + */ +static VALUE +rbjail_s_alloc( VALUE class ) +{ + return Data_Wrap_Struct( class, NULL, rbjail_gc_free, NULL ); +} + + +/* + * Create a new jail object. + * Returns the +id+ of the newly created jail. + */ +static VALUE +rbjail_jail( int argc, VALUE *argv, VALUE self ) { + struct jail j; + struct in_addr in; + VALUE ip, path, host, securelevel; + int id; + + rb_scan_args( argc, argv, "31", &ip, &path, &host, &securelevel ); + + 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 ); + + SafeStringValue(path); + SafeStringValue(host); + + j.version = 0; + j.path = RSTRING_PTR( path ); + j.hostname = RSTRING_PTR( host ); + j.ip_number = ntohl( in.s_addr ); + id = jail(&j); + + if ( id == -1 ) rb_sys_fail( "jail" ); + if ( chdir("/") == -1 ) rb_sys_fail( "chdir" ); + + 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. + */ +static VALUE +rbjail_find( int argc, VALUE *argv, VALUE self ) +{ + struct xprison *sxp, *xp; + struct in_addr in; + size_t i, len; + + VALUE arg, rbjail; + VALUE jails = rb_ary_new(); + int jid = 0, compare = 0; + char *str = ""; + + rb_scan_args( argc, argv, "01", &arg ); + + // An argument was passed, so let's figure out what it was + // and try to compare it to the current jails. + // + if ( argc == 1 ) { + switch ( TYPE(arg) ) { + + // find by JID + // + case T_FIXNUM: + jid = FIX2INT( arg ); + break; + + // find by IP/hostname + // + case T_OBJECT: + case T_DATA: + case T_STRING: + str = RSTRING_PTR( rb_obj_as_string(arg) ); + compare = 1; + break; + + default: + rb_raise( rb_eTypeError, "invalid argument to find(): %s", + RSTRING_PTR(rb_inspect(arg)) ); + } + } + + // Get the size of the xprison and allocate memory to it. + // + if ( sysctlbyname("security.jail.list", NULL, &len, NULL, 0) == -1 ) + rb_sys_fail("sysctlbyname(): security.jail.list"); + if ( len <= 0 ) { + rb_sys_fail("sysctlbyname(): unable to determine xprison size"); + return Qnil; + } + + sxp = xp = malloc( len ); + if ( sxp == NULL ) { + rb_sys_fail("sysctlbyname(): unable to allocate memory"); + return Qnil; + } + + // Get and sanity check the current prison list + // + if ( sysctlbyname("security.jail.list", xp, &len, NULL, 0) == -1 ) { + if ( errno == ENOMEM ) free( sxp ); + rb_sys_fail("sysctlbyname(): out of memory"); + return Qnil; + } + 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 + // + if ( argc == 0 ) { + for ( i = 0; i < len / sizeof(*xp); i++ ) { + rb_ary_push( jails, rbjail_alloc( self, xp ) ); + xp++; + } + + free( sxp ); + return jails; + } + + // Argument passed to find(): walk the jail list, comparing the arg + // with each current jail. Return first match. + // + 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 )) + )) { + + debugMsg(( "Located jail: %d", xp->pr_id )); + rbjail = rbjail_alloc( self, xp ); + + free( sxp ); + return rbjail; + } + else { + + xp++; + } + } + + free( sxp ); + return Qnil; +} + + +static void +rbjail_do_jail_attach( int jid ) +{ + if ( jail_attach(jid) == -1 ) + rb_sys_fail( "jail_attach" ); +} + + +/* Mostly ripped off from Ruby's process.c */ +static VALUE +rbjail_attach_block( int jid ) +{ + int pid; + + rb_secure(2); + + fflush(stdout); + fflush(stderr); + + switch ( pid = fork() ) { + case 0: + rb_thread_atfork(); + if ( rb_block_given_p() ) { + int status; + + rbjail_do_jail_attach( jid ); + rb_protect( rb_yield, Qundef, &status ); + ruby_stop( status ); + } + return Qnil; + + case -1: + rb_sys_fail( "fork(2)" ); + return Qnil; + + default: + return INT2FIX( pid ); + } +} + +static VALUE +rbjail_attach( int argc, VALUE *argv, VALUE self ) +{ + VALUE jidnum, rval; + int jid; + + rb_scan_args( argc, argv, "1", &jidnum ); + jid = NUM2INT( jidnum ); + + if ( rb_block_given_p() ) { + rval = rbjail_attach_block( jid ); + } + + else { + rbjail_do_jail_attach( jid ); + rval = Qtrue; + } + + return rval; +} + + +/* + * Ruby initializer. + */ +void +Init_jail( void ) +{ + 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") ); + + 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 + // + 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, "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 ); +} + +