ext/bsdjail.c
author Michael Granger <ged@FaerieMUD.org>
Thu, 20 Nov 2008 17:12:39 +0000
changeset 5 41a1542b3b10
parent 2 0c24586f579a
permissions -rw-r--r--
Cleaned up bsdjail extension code a bit more and made the extconf check for the actual headers being included.

/*
 *  bsdjail.c - JParallel extension for FreeBSD jail(2) functions
 *  $Id$
 *  
 *  Authors:
 *    * Michael Granger <ged@FaerieMUD.org>
 *    * Mahlon E. Smith <mahlon@martini.nu>
 *  
 *  Copyright (c) 2008, Michael Granger and Mahlon 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 "bsdjail.h"

VALUE rbjail_mBSD;
VALUE rbjail_cBSDJail;


/*
 *  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 );
}


/*
struct jail {
	u_int32_t       version;
	char            *path;
	char            *hostname;
	u_int32_t       ip_number;
};
*/

/*
 * Allocation function
 */
static jail *
rbjail_jail_alloc() {
	jail *ptr = ALLOC( jail );
	
	ptr->version	= 0;
	ptr->path		= NULL;
	ptr->hostname	= NULL;
	ptr->ip_number	= 0;
	
	debugMsg(( "Initialized a jail pointer <%p>", ptr ));
	return ptr;
}


/*
 * GC Free function
 */
static void
rbjail_gc_free( jail *ptr ) {
	if ( ptr ) {
		if ( ptr->path ) xfree( ptr->path );
		if ( ptr->hostname ) xfree( ptr->hostname );
		
		ptr->path		= NULL;
		ptr->hostname	= NULL;

		xfree( ptr );
	}
	
	else {
		debugMsg(( "Not freeing an uninitialized jail" ));
	}
}


/*
 * Object validity checker. Returns the data pointer.
 */
static jail *
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 jail *
rbjail_get_jail( VALUE self ) {
	jail *ptr = check_sentence( self );

	debugMsg(( "Fetching a Jail (%p).", ptr ));
	if ( !ptr )
		rb_raise( rb_eRuntimeError, "uninitialized Sentence" );

	return ptr;
}


/* --------------------------------------------------------------
 * Jail utility functions
 * -------------------------------------------------------------- */

/*
 * Try to jail_attach() to the specified +jid+, raising an exception if it fails.
 */
static void
rbjail_do_jail_attach( int jid )
{
	if ( jail_attach(jid) == -1 )
		rb_sys_fail( "jail_attach" );
}


/* 
 * Fork + Block function for rbjail_attach(). Mostly stolen 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 );
	}
}


/* --------------------------------------------------------------
 * Class methods
 * -------------------------------------------------------------- */

/*
 *  call-seq:
 *     BSD::Jail.allocate   -> bsdjail
 *
 *  Allocate a new BSD::Jail object.
 *
 */
static VALUE
rbjail_jail_s_allocate( VALUE klass ) {
	return Data_Wrap_Struct( klass, 0, rbjail_gc_free, 0 );
}


/*
 *  call-seq:
 *     BSD::Jail.list   -> array
 *
 *  Return an Array of all the running jails on the host.
 *
 */
static VALUE
rbjail_jail_s_list( VALUE klass ) {
	VALUE rval = rb_ary_new();
	struct xprison *xp;
	struct in_addr in;
	size_t i, len;

	if ( sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1 )
		rb_sys_fail( "sysctlbyname(): jail.list" );

	xp = ALLOCA_N( xprison, 1 );

	if ( sysctlbyname("jail.list", xp, &len, NULL, 0) == -1 ) {
		rb_sys_fail( "sysctlbyname(): jail.list" );
	}

	if ( len < sizeof(*xp) || len % sizeof(*xp) ||
		xp->pr_version != KINFO_PRISON_VERSION )
		rb_fatal( "Kernel and userland out of sync" );

	len /= sizeof( *xp );
	for ( i = 0; i < len; i++ ) {
		VALUE jail, args[3];
		
		/* Hostname */
		args[0] = rb_str_new2( xp->pr_host );

		/* IP */
		in.s_addr = ntohl( xp->pr_ip );
		args[1] = rb_str_new2( inet_ntoa(in) );
		
		/* Path */
		args[2] = rb_str_new2( xp->pr_path );
		
		jail = rb_class_new_instance( 3, args, klass );
		rb_ary_push( rval, jail );

		xp++;
	}

	return rval;
}




/* --------------------------------------------------------------
 * Instance methods
 * -------------------------------------------------------------- */

/*
 *  call-seq:
 *     BSD::Jail.new( hostname, ip, path )           -> new_jail
 *
 *  Create a new jail for the given +hostname+, +ip+, and +path+.
 */
static VALUE
rbjail_jail_initialize( VALUE self, VALUE hostname, VALUE ip, VALUE path ) {
	if ( !rbjail_check_jail(self) ) {
		struct jail *ptr = rbjail_jail_alloc();
		char *pathstr = NULL, *hostnamestr = NULL;

		Check_Type( hostname, T_STRING );
		Check_Type( ip, T_STRING );

		pathstr = ALLOC_N( char, RSTRING(path)->len );
		strncpy( pathstr, RSTRING(path)->ptr, RSTRING(path)->len );

		hostnamestr = ALLOC_N( char, RSTRING(hostname)->len );
		strncpy( hostnamestr, RSTRING(hostname)->ptr, RSTRING(hostname)->len );
		
		/*
		struct jail {
			u_int32_t       version;
			char            *path;
			char            *hostname;
			u_int32_t       ip_number;
		};
		*/
		ptr->version = 0;   /* Per the manpage's recommendation */
		ptr->ip_number = inet_addr( StringValuePtr(ip) );
		ptr->path = path;
		ptr->hostname = hostname;
	}
}


/*
 *  call-seq:
 *     jail.attach             -> true or false
 *     jail.attach { block }   -> pid
 *
 *  Attach to the given jail. In the non-block form, attach the current process to the
 *  jail and return +true+ if it succeeds. This is a one-way operation, and requires root
 *  privileges.
 *  
 *  In the block form, the process will be forked, and the block will be attached to the jail and 
 *  run by the child. The parent process will receive the process ID of the child.
 */
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;
}


/*
 * I can't remember how the hell you document a class, but that will go here.
 */
void
Init_bsdjail( void ) {
	rbjail_mBSD = rb_define_module( "BSD" );
	rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail" );

	/* Class methods */
	rb_define_alloc_function( rbjail_cBSDJail, rbjail_jail_s_allocate );
	rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail_s_jail, 0 );
	rb_define_singleton_method( rbjail_cBSDJail, "list", rbjail_jail_s_list, 0 );
	
	/* Instance methods */
	rb_define_method( rbjail_cBSDJail, "initialize", rbjail_jail_initialize, 3 );
	rb_define_method( rbjail_cBSDJail, "attach", rbjail_jail_attach, -1 );
	rb_define_method( rbjail_cBSDJail, "hostname", rbjail_jail_hostname, 0 );
	rb_define_method( rbjail_cBSDJail, "path", rbjail_jail_path, 0 );
	rb_define_method( rbjail_cBSDJail, "ip", rbjail_jail_ip, 0 );
	rb_define_method( rbjail_cBSDJail, "jid", rbjail_jail_jid, 0 );
	
}