--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/jail.c Sat Feb 28 04:54:46 2009 +0000
@@ -0,0 +1,439 @@
+/*
+ * jail.c - Ruby jparallel
+ *
+ * vim: set nosta noet ts=4 sw=4:
+ *
+ * $Id$
+ *
+ * Authors:
+ * * Michael Granger <ged@FaerieMUD.org>
+ * * Mahlon E. Smith <mahlon@martini.nu>
+ *
+ * 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 );
+}
+
+