ext/jail.c
author Mahlon E. Smith <mahlon@martini.nu>
Sat, 28 Feb 2009 04:54:46 +0000
branchmahlon-misc
changeset 12 5fd07a9e7e7b
parent 11 ext/bsdjail.c@e908d309e7ec
permissions -rw-r--r--
* Added jail header dependencies for mkmf Makefile. * Renamed header file (bsdjail -> jail) * Fixed copyright * Initial commit of primary functionality * Attach to a running jail * Create a new jail * List and instantiate existing jails * Fetch information about existing jails * Still needs... * Attach "in a block" fleshed out * Documentation and better comments * Tests (of some kind, this will be tough) * Update for not-yet-MFC'ed multiple IP jail patch in FBSD 7.2-STABLE

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