ext/jail.c
branchmahlon-misc
changeset 12 5fd07a9e7e7b
parent 11 e908d309e7ec
--- /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 );
+}
+
+